import asyncio
import base64
import json
import os
import platform
import shutil
import time
from datetime import datetime
from pathlib import Path

from loguru import logger
from playwright.async_api import async_playwright

from core.command_manager import CommandRes


class BrowserManager:
    """
    管理浏览器启动、登录状态、cookie、关闭等操作
    优化后支持：
    1. 自动重试机制
    2. 更好的错误处理
    3. 状态同步
    4. 登录状态自动恢复
    """

    def __init__(self, state, user_data_dir=None):
        if platform.system() == "Windows":
            base_dir = os.path.join(os.getcwd(), "user_data")
        else:
            base_dir = "/opt/user_data"
            
            # ✅ 在Linux系统上，显式设置Playwright浏览器路径
            # 避免在服务器上因环境变量未加载导致找不到浏览器
            if not os.environ.get("PLAYWRIGHT_BROWSERS_PATH"):
                playwright_browsers_path = "/opt/playwright-browsers"
                os.environ["PLAYWRIGHT_BROWSERS_PATH"] = playwright_browsers_path
                logger.info(f"设置 PLAYWRIGHT_BROWSERS_PATH={playwright_browsers_path}")

        self.user_data_dir = user_data_dir or os.path.join(base_dir, "default")
        os.makedirs(self.user_data_dir, exist_ok=True)

        # 创建截图目录
        self.screenshots_dir = os.path.join(os.getcwd(), "screenshots")
        os.makedirs(self.screenshots_dir, exist_ok=True)

        self.state = state
        self.playwright = None
        self._playwright_context = None  # 保存 playwright 上下文管理器，用于正确关闭
        self.browser = None
        self.page = None
        self._browser_lock = asyncio.Lock()
        self._starting = False
        self._cleanup_in_progress = False  # 标记是否正在清理，防止重复触发
        self.tcp_client = None  # TCP客户端引用，用于发送消息
        self._monitor_task = None  # 浏览器监控任务
        self._max_screenshots = 50  # 最多保留的截图数量
        
        # 登录监控控制
        self._qrcode_watch_task = None  # 二维码监控任务
        self._login_success_notified = False  # 标记是否已通知登录成功
        self._qrcode_scan_detected = False  # 标记是否检测到扫码成功（通过响应监听）
        self._qrcode_redirect_url = None  # 扫码成功后的跳转URL
        self._qrcode_response_handler = None  # 二维码响应监听器
    
    def set_tcp_client(self, tcp_client):
        """设置TCP客户端引用"""
        self.tcp_client = tcp_client
    
    async def _start_browser_monitor(self):
        """启动浏览器状态监控（定期检查浏览器是否被手动关闭）"""
        logger.info("启动浏览器状态监控")
        while True:
            try:
                await asyncio.sleep(3)  # 每3秒检查一次
                
                # 如果没有浏览器实例，跳过检查
                if not self.browser or not self.page:
                    continue
                
                # 尝试获取页面标题来检测浏览器是否还在运行
                try:
                    await self.page.title()
                except Exception as e:
                    # 区分"页面导航"和"浏览器真正关闭"
                    error_msg = str(e).lower()
                    
                    # ✅ 这些错误是因为页面导航，不是浏览器关闭，应该忽略
                    if "execution context was destroyed" in error_msg:
                        logger.debug(f"页面正在导航，执行上下文暂时销毁（正常现象）")
                        continue
                    
                    # ❌ 这些错误才是真正的浏览器关闭
                    if "closed" in error_msg or "target page" in error_msg or "browser has been closed" in error_msg:
                        logger.warning(f"检测到浏览器已关闭: {e}")
                        await self._on_browser_closed()
                        break  # 退出监控循环
                    
                    # ⚠️ 其他未知错误，记录但不触发关闭
                    logger.debug(f"浏览器监控检测到异常（已忽略）: {e}")
            except asyncio.CancelledError:
                logger.info("浏览器监控任务被取消")
                break
            except Exception as e:
                logger.error(f"浏览器监控任务异常: {e}")
    
    def _stop_browser_monitor(self):
        """停止浏览器状态监控"""
        if self._monitor_task and not self._monitor_task.done():
            self._monitor_task.cancel()
            logger.info("已停止浏览器状态监控")
        
    # ----------------------------------------------------------------------
    # 启动逻辑
    # ----------------------------------------------------------------------
    async def start_browser(self, retry_on_failure=True) -> dict:
        """复用旧浏览器配置启动"""
        async with self._browser_lock:
            if self.browser and not self.browser.pages[0].is_closed():
                logger.info("浏览器已运行，无需重复启动")
                return CommandRes.Start().Success(msg="浏览器已启动")

            if self._starting:
                logger.warning("浏览器正在启动中...")
                return CommandRes.Start().Fail(msg="浏览器正在启动中")

            self._starting = True
            try:
                logger.info(f"启动浏览器（复用模式），数据目录: {self.user_data_dir}")
                self.state.update(browser_status="starting")
                
                # ========== 强制非headless模式 ==========
                # 闲鱼会检测headless模式并拒绝访问，显示"非法访问"提示
                # 因此所有环境都必须使用非headless模式
                import os
                import subprocess
                
                # 检查是否强制使用headless（仅供高级用户调试使用）
                force_headless = os.environ.get("FORCE_HEADLESS", "").lower() in ["1", "true", "yes"]
                
                if force_headless:
                    logger.warning("⚠️  检测到FORCE_HEADLESS环境变量，将使用headless模式（可能导致闲鱼拒绝访问）")
                    use_headless = True
                else:
                    # 所有环境强制非headless模式
                    use_headless = False
                    
                    if platform.system() == "Windows":
                        logger.info("✅ Windows系统，使用非headless模式")
                    else:
                        # Linux系统：检查Xvfb环境
                        display = os.environ.get("DISPLAY")
                        
                        if not display:
                            error_msg = "=" * 70 + """
❌ 错误：未检测到DISPLAY环境变量！

Linux服务器需要Xvfb虚拟显示才能运行非headless浏览器
否则闲鱼会检测为'非法访问'并拒绝服务

解决方案：

1️⃣  检查Xvfb是否已安装:
   which Xvfb

2️⃣  如果未安装，请安装Xvfb:
   CentOS 9: sudo dnf install -y xorg-x11-server-Xvfb
   Ubuntu:   sudo apt install -y xvfb

3️⃣  启动Xvfb:
   Xvfb :99 -screen 0 1920x1080x24 > /dev/null 2>&1 &
   export DISPLAY=:99

4️⃣  或者使用我们提供的启动脚本:
   ./start_with_xvfb.sh

""" + "=" * 70
                            logger.error(error_msg)
                            raise RuntimeError("Linux环境缺少DISPLAY环境变量，无法启动浏览器")
                        
                        # 检查Xvfb是否真的在运行
                        try:
                            # 尝试检测Xvfb进程
                            xvfb_running = subprocess.run(
                                ["pgrep", "-f", "Xvfb"],
                                capture_output=True,
                                timeout=2
                            ).returncode == 0
                            
                            if xvfb_running:
                                logger.info(f"✅ 检测到DISPLAY={display}，Xvfb正在运行，使用非headless模式")
                            else:
                                logger.warning(f"⚠️  DISPLAY={display}，但未检测到Xvfb进程")
                                logger.warning("⚠️  如果浏览器启动失败，请确认Xvfb已启动")
                        except Exception as e:
                            logger.debug(f"无法检测Xvfb进程: {e}")
                            logger.info(f"✅ 检测到DISPLAY={display}，使用非headless模式")
                
                # ✅ 启动Playwright，捕获可能的连接异常
                try:
                    # ✅ 保存上下文管理器，以便在清理时正确关闭
                    self._playwright_context = async_playwright()
                    self.playwright = await self._playwright_context.start()
                except Exception as e:
                    # 如果启动失败（如连接关闭），清理状态并重新抛出
                    self._starting = False
                    self.state.update(browser_status="stopped")
                    self._playwright_context = None
                    logger.error(f"启动Playwright失败: {e}")
                    raise
                
                # ✅ 优先使用系统的 Chrome 稳定版（而不是 Chromium for Testing）
                # 这样可以避免被检测为 "Google Chrome for Testing"
                launch_options = {
                    "user_data_dir": self.user_data_dir,
                    "headless": use_headless,
                    "args": [
                        "--no-sandbox", 
                        "--disable-blink-features=AutomationControlled",  # ✅ 禁用"自动化控制"横幅提示和 webdriver
                        "--exclude-switches=enable-automation",  # ✅ 排除自动化标记（移除横幅的关键参数，必须放在前面）
                        "--disable-infobars",  # ✅ 禁用信息栏（移除横幅，必须放在前面）
                        "--disable-dev-shm-usage",
                        "--disable-gpu",
                        "--disable-features=IsolateOrigins,site-per-process",  # 减少检测特征
                        "--window-size=1920,1080",  # 设置合理的窗口大小
                        "--start-maximized",  # 最大化窗口（更像真实用户）
                        "--disable-notifications",  # 禁用通知
                        "--lang=zh-CN",  # 设置语言
                    ],
                    "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
                    "viewport": {"width": 1920, "height": 1080},
                    "locale": "zh-CN",
                    "timezone_id": "Asia/Shanghai",
                }
                
                # ✅ Windows 系统：尝试使用系统的 Chrome 稳定版
                if platform.system() == "Windows":
                    try:
                        # 尝试使用系统已安装的 Chrome 稳定版（而不是 Playwright 自带的 Chromium）
                        # channel='chrome' 会查找系统的 Chrome 稳定版
                        launch_options["channel"] = "chrome"
                        logger.info("✅ 尝试使用系统 Chrome 稳定版（而非 Chromium for Testing）")
                    except Exception as e:
                        logger.debug(f"无法使用系统 Chrome，使用默认 Chromium: {e}")
                
                self.browser = await self.playwright.chromium.launch_persistent_context(**launch_options)
                
                # 获取或创建页面
                pages = self.browser.pages
                if pages:
                    self.page = pages[0]
                else:
                    self.page = await self.browser.new_page()
                
                # ✅ 使用 CDP (Chrome DevTools Protocol) 注入完整反检测脚本
                # ✅ 已验证：这个配置能通过 Fingerprint.com 和 bot-detector.rebrowser.net 的检测
                # 关键：使用 CDP 在底层注入，比 add_init_script 更早执行，更难被检测
                # 注意：虽然使用 launch_persistent_context 保留用户数据（Cookie），
                # 但 CDP 脚本在页面加载前执行，能有效绕过大部分检测
                try:
                    from stealth_config import CDP_STEALTH_SCRIPT
                    cdp_session = await self.page.context.new_cdp_session(self.page)
                    await cdp_session.send('Page.addScriptToEvaluateOnNewDocument', {
                        'source': CDP_STEALTH_SCRIPT
                    })
                    logger.debug("✅ CDP 反检测脚本已注入（完整版本，包含 navigator 属性设置）")
                except Exception as e:
                    logger.warning(f"⚠️  CDP 脚本注入失败: {e}")
                    # 降级方案：如果 CDP 失败，使用 add_init_script
                    try:
                        from stealth_config import PLAYWRIGHT_STEALTH_SCRIPT
                        await self.page.add_init_script(PLAYWRIGHT_STEALTH_SCRIPT)
                        logger.debug("✅ 已注入备用反检测脚本（WebGL/Canvas/AudioContext）")
                    except Exception as e2:
                        logger.warning(f"⚠️  备用反检测脚本注入失败: {e2}")
                
                # 设置默认超时
                self.page.set_default_timeout(30000)
                
                # 访问首页
                try:
                    await self.page.goto("https://www.goofish.com/personal", timeout=20000, wait_until="domcontentloaded")
                except Exception as e:
                    logger.warning(f"访问首页失败: {e}，继续执行")
                
                logger.info("浏览器启动完成，等待页面稳定...")
                self.state.update(browser_status="running")
                
                # 启动浏览器状态监控（检测手动关闭）
                self._monitor_task = asyncio.create_task(self._start_browser_monitor())
                
                # ✅ 主动将state中的登录保持相关cookie加载到浏览器
                # 这些cookie可能在state中但不在浏览器的持久存储中
                await self._sync_login_keep_cookies_to_browser()
                
                # ⚠️ 快速检测登录Cookie并验证状态
                cookies = await self.get_cookie_dict()
                has_cookie = cookies and '_m_h5_tk' in cookies
                has_login_keep_cookies = any(
                    cookie_name in cookies 
                    for cookie_name in ["havana_lgc2_77", "_hvn_lgc_", "havana_lgc_exp", "last_u_xianyu_xianyu_sdkSilent"]
                )
                
                if has_cookie or has_login_keep_cookies:
                    logger.info("检测到登录Cookie或登录保持Cookie，主动验证登录状态...")
                    # 主动验证登录（防止cookie已失效但状态没更新）
                    await asyncio.sleep(2)  # 等待浏览器稳定
                    is_logged = await self.check_login()
                    
                    if not is_logged and (has_login_keep_cookies or has_cookie):
                        # ✅ 登录检测失败，但可能有登录保持cookie，尝试点击"快速进入"按钮
                        logger.info("登录检测失败，检查是否有登录框和'快速进入'按钮...")
                        try:
                            # 刷新页面确保登录框显示
                            await self.page.reload(wait_until="domcontentloaded", timeout=30000)
                            await asyncio.sleep(3)  # 等待页面加载完成
                            
                            # 检查是否有登录框
                            login_box = await self.page.query_selector("#alibaba-login-box")

                            if login_box:
                                clicked = False
                                logger.info("✅ 检测到登录框，尝试点击'快速进入'按钮...")
                                frame = await login_box.content_frame()
                                if frame:
                                    # 检测 has-login 页面（快速进入）
                                    has_login_div = await frame.query_selector("body .has-login")
                                    if has_login_div:
                                        quick_button = await frame.query_selector(
                                            "button.fm-button.fm-submit:has-text('快速进入')")
                                        if quick_button:
                                            logger.info("检测到快速进入按钮，执行点击")
                                            await quick_button.click(timeout=5000)
                                            clicked = True
                                        else:
                                            logger.debug("未找到快速进入按钮")
                                    else:
                                        logger.debug("普通登录界面，非快速进入页面")

                                if clicked:
                                    logger.info("✅ 已点击'快速进入'按钮，等待登录完成...")
                                    # 等待登录完成，最多等待10秒
                                    for i in range(5):
                                        await asyncio.sleep(2)
                                        is_logged = await self.check_login()
                                        if is_logged:
                                            logger.success(f"✅ 通过'快速进入'按钮登录成功: {self.state.get('account')}")
                                            break

                                    if not is_logged:
                                        logger.warning("⚠️ 点击'快速进入'按钮后仍未登录成功")
                                else:
                                    logger.warning("⚠️ 未找到'快速进入'按钮，可能需要手动登录")
                                    # 截图用于调试
                                    try:
                                        await self.capture_screenshot("check_login", "quick_access_not_found")
                                    except:
                                        pass
                            else:
                                logger.debug("未检测到登录框")
                        except Exception as e:
                            logger.warning(f"尝试点击'快速进入'按钮时出错: {e}")
                            # 继续执行，不影响其他逻辑
                    
                    if is_logged:
                        logger.success(f"✅ 登录状态有效: {self.state.get('account')}")
                    else:
                        logger.warning("⚠️ Cookie已失效，登录状态已更新为未登录")
                else:
                    logger.debug("无登录Cookie，浏览器已启动")
                
                return CommandRes.Start().Success(msg="浏览器启动成功")
                
            except Exception as e:
                logger.error(f"浏览器启动失败: {e}")
                self.state.update(browser_status="error", last_error=str(e))
                await self._cleanup_browser()
                
                if retry_on_failure:
                    logger.info("5秒后重试启动...")
                    await asyncio.sleep(5)
                    return await self.start_browser(retry_on_failure=False)
                
                return CommandRes.Start().Fail(msg=f"浏览器启动失败: {e}")
            finally:
                self._starting = False

    async def start_new_browser(self) -> dict:
        """删除旧配置后全新启动（增强清理，确保无残留）"""
        async with self._browser_lock:
            # ⚠️ 停止所有监控任务（包括二维码监控）
            if self._qrcode_watch_task and not self._qrcode_watch_task.done():
                logger.warning("正在停止旧的二维码监控任务...")
                self._qrcode_watch_task.cancel()
                try:
                    await self._qrcode_watch_task
                except asyncio.CancelledError:
                    logger.info("二维码监控任务已停止")
            
            # 先关闭现有浏览器
            await self._cleanup_browser()
            
            # ✅ 增强清理：等待浏览器进程完全退出（防止文件占用）
            logger.info("等待浏览器进程完全退出...")
            await asyncio.sleep(3)  # 从1秒改为3秒，确保进程完全退出
            
            # 删除用户数据目录（增强版）
            if os.path.exists(self.user_data_dir):
                logger.warning(f"检测到旧浏览器数据目录，删除中: {self.user_data_dir}")
                
                # ✅ 重试机制：删除可能需要多次尝试（Windows文件锁）
                max_retries = 3
                for retry in range(max_retries):
                    try:
                        # ⚠️ 改为 ignore_errors=False，这样可以看到删除失败的真实原因
                        shutil.rmtree(self.user_data_dir, ignore_errors=False)
                        logger.info("✅ 用户数据目录删除成功")
                        break
                    except PermissionError as e:
                        if retry < max_retries - 1:
                            logger.warning(f"删除失败（文件被占用），{2}秒后重试... ({retry+1}/{max_retries})")
                            await asyncio.sleep(2)
                        else:
                            logger.error(f"❌ 删除用户数据目录失败（已重试{max_retries}次）: {e}")
                            logger.warning("⚠️ 可能有浏览器进程未完全退出，请手动检查进程")
                            # ✅ 最后一次尝试：强制删除（忽略错误）
                            try:
                                shutil.rmtree(self.user_data_dir, ignore_errors=True)
                            except:
                                pass
                    except Exception as e:
                        logger.error(f"删除用户数据目录失败: {e}")
                        break
                
                # ✅ 再等待一段时间，确保文件系统完成删除
                await asyncio.sleep(1)
            
            # ✅ 验证删除是否成功
            if os.path.exists(self.user_data_dir):
                logger.warning("⚠️ 用户数据目录仍然存在，尝试清空内部文件...")
                try:
                    # 如果无法删除目录，至少清空内部文件
                    for item in os.listdir(self.user_data_dir):
                        item_path = os.path.join(self.user_data_dir, item)
                        try:
                            if os.path.isfile(item_path):
                                os.unlink(item_path)
                            elif os.path.isdir(item_path):
                                shutil.rmtree(item_path, ignore_errors=True)
                        except:
                            pass
                    logger.info("✅ 已清空目录内部文件")
                except Exception as e:
                    logger.error(f"清空目录失败: {e}")
            
            # 确保目录存在
            os.makedirs(self.user_data_dir, exist_ok=True)
            logger.info("✅ 启动全新浏览器环境...")
        
        # ✅ 重要：清空state.json文件（防止重启后恢复旧cookie）
        try:
            state_json_path = os.path.join(os.getcwd(), "state.json")
            if os.path.exists(state_json_path):
                logger.warning(f"删除state.json文件以彻底清除cookie: {state_json_path}")
                os.unlink(state_json_path)
                logger.info("✅ state.json文件已删除")
        except Exception as e:
            logger.error(f"删除state.json失败: {e}")

        # 提前清空cookie，因为mark_logout()会保留cookie
        self.state.set('cookie', None)
        # 清空登录状态和重置标记
        self.state.mark_logout()
        self._login_success_notified = False  # 重置通知标记
        
        # ✅ 保存清空后的状态（重新创建一个干净的state.json）
        self.state.save()
        
        return await self.start_browser()

    async def _on_browser_closed(self):
        """浏览器关闭事件处理（手动关闭或崩溃）"""
        # ✅ 防止在清理过程中重复触发
        if self._cleanup_in_progress:
            logger.debug("清理正在进行中，跳过浏览器关闭事件处理")
            return
            
        # 防止重复执行
        if not self.browser and not self.page:
            return
        
        logger.warning("检测到浏览器已关闭")
        
        # 停止监控任务
        self._stop_browser_monitor()
        
        # 清理资源
        self.browser = None
        self.page = None
        
        # ✅ 只更新浏览器状态，不改变登录状态！
        # 因为Cookie还在，可以继续使用API（搜索、下单等）
        self.state.update(browser_status="stopped")
        logger.info("浏览器状态已更新为已停止，登录状态保持不变（Cookie依然有效）")
        
        # 主动通知控制端
        if self.tcp_client:
            try:
                await self.tcp_client.send_status()
                logger.info("已通知控制端：浏览器已关闭")
            except Exception as e:
                logger.warning(f"通知控制端失败: {e}")
    
    async def capture_screenshot(self, operation: str = "unknown", reason: str = "timeout", timeout: int = 5) -> str:
        """
        截取当前页面截图（超时或异常时调用）
        
        Args:
            operation: 操作类型（如 'login', 'search', 'order' 等）
            reason: 截图原因（如 'timeout', 'error', 'verification' 等）
            timeout: 截图超时时间（秒），默认5秒（缩短超时）
            
        Returns:
            截图文件路径，如果失败返回空字符串
        """
        try:
            if not self.page or self.page.is_closed():
                logger.debug("无法截图：页面未打开或已关闭")
                return ""
            
            # ✅ 检查页面是否响应（快速ping）
            try:
                # 快速检查页面是否还活着
                await asyncio.wait_for(self.page.title(), timeout=2)
            except (asyncio.TimeoutError, Exception) as e:
                logger.warning(f"页面可能已卡死，跳过截图: {e}")
                return ""
            
            # ✅ 不等待DOM加载（可能已经卡死），直接截图
            # 移除 wait_for_load_state，因为这会导致超时
            
            # 生成截图文件名：时间戳_操作_原因.png
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            filename = f"{timestamp}_{operation}_{reason}.png"
            filepath = os.path.join(self.screenshots_dir, filename)
            
            # ✅ 截图时设置超时时间（减少默认的30秒超时）
            try:
                await self.page.screenshot(
                    path=filepath,
                    full_page=False,  # 先不使用全页面，避免超时
                    timeout=timeout * 1000
                )
                logger.warning(f"📸 已截图保存: {filepath}")
                
                # 清理旧截图（保留最近的N张）
                asyncio.create_task(self._cleanup_old_screenshots())
                
                return filepath
            except asyncio.TimeoutError:
                logger.warning(f"截图超时（{timeout}秒），跳过截图")
                return ""
            
        except Exception as e:
            logger.warning(f"截图失败: {e}，跳过截图")
            return ""
    
    async def _cleanup_old_screenshots(self):
        """清理旧截图文件，只保留最近的N张"""
        try:
            screenshots = []
            for file in Path(self.screenshots_dir).glob("*.png"):
                screenshots.append((file.stat().st_mtime, file))
            
            # 按修改时间排序
            screenshots.sort(reverse=True)
            
            # 删除超出限制的旧截图
            if len(screenshots) > self._max_screenshots:
                for _, old_file in screenshots[self._max_screenshots:]:
                    try:
                        old_file.unlink()
                        logger.debug(f"已删除旧截图: {old_file.name}")
                    except:
                        pass
                        
        except Exception as e:
            logger.error(f"清理旧截图失败: {e}")
    
    async def _cleanup_browser(self):
        """内部清理浏览器资源（带超时保护）"""
        try:
            # ✅ 标记正在清理，防止监控任务触发 _on_browser_closed
            self._cleanup_in_progress = True
            
            # 停止监控任务
            self._stop_browser_monitor()
            
            # ✅ 先关闭浏览器，再关闭Playwright（带超时保护）
            if self.browser:
                try:
                    # ✅ 使用超时保护，避免关闭浏览器时卡住
                    await asyncio.wait_for(self.browser.close(), timeout=2.0)
                    logger.debug("浏览器实例已关闭")
                except asyncio.TimeoutError:
                    logger.warning("关闭浏览器超时，强制清理")
                except Exception as e:
                    logger.debug(f"关闭浏览器时出错: {e}")
                finally:
                    self.browser = None
                    self.page = None
            
            # ✅ 使用上下文管理器的 __aexit__ 正确关闭 Playwright
            if self._playwright_context and self.playwright:
                try:
                    # ✅ 使用超时保护，调用上下文管理器的退出方法
                    await asyncio.wait_for(
                        self._playwright_context.__aexit__(None, None, None),
                        timeout=2.0
                    )
                    logger.debug("Playwright已停止")
                except asyncio.TimeoutError:
                    logger.warning("关闭Playwright超时，强制清理")
                except asyncio.CancelledError:
                    logger.debug("Playwright关闭被取消")
                except Exception as e:
                    logger.debug(f"关闭Playwright时出错: {e}")
                finally:
                    self.playwright = None
                    self._playwright_context = None
                    
                    # ✅ 清理 Playwright 相关的未完成任务，避免 "Future exception was never retrieved" 警告
                    try:
                        # 给事件循环一点时间来清理残留任务
                        await asyncio.sleep(0.5)
                    except:
                        pass
            elif self.playwright:
                # ✅ 如果没有上下文管理器（旧代码兼容），使用 stop() 方法
                try:
                    await asyncio.wait_for(self.playwright.stop(), timeout=2.0)
                    logger.debug("Playwright已停止")
                except:
                    pass
                finally:
                    self.playwright = None
            
            # ✅ 重置启动状态
            self._starting = False
            self.state.update(browser_status="stopped")
            
            logger.debug("浏览器资源已清理")
        except Exception as e:
            logger.warning(f"清理浏览器资源时出错: {e}")
        finally:
            # ✅ 清理完成，重置标志
            self._cleanup_in_progress = False

    # ----------------------------------------------------------------------
    # 停止逻辑
    # ----------------------------------------------------------------------
    async def handle_stop(self, msg=None):
        """
        停止浏览器（但不改变登录状态，Cookie依然有效）
        
        ✅ 同时停止所有正在进行的任务（搜索、下单等）
        """
        async with self._browser_lock:
            await self._cleanup_browser()
            
            # ✅ 停止所有正在进行的任务（搜索、下单等）
            work_status = self.state.get("work_status")
            if work_status in ["searching", "ordering"]:
                logger.info(f"⚠️  停止正在进行的任务（work_status={work_status}）")
                self.state.set("work_status", "idle")  # 设置为idle，中断所有API请求
            
            # ✅ 只更新浏览器状态，不改变登录状态
            self.state.update(browser_status="stopped")
            logger.info("浏览器已关闭（登录状态保持不变，Cookie依然有效）")
            
            # 主动发送状态更新到控制端
            if self.tcp_client:
                try:
                    await self.tcp_client.send_status()
                    logger.info("已发送浏览器关闭状态到控制端")
                except Exception as e:
                    logger.warning(f"发送状态更新失败: {e}")
        
        return CommandRes.Stop().Success(msg="浏览器已关闭")
    
    # ----------------------------------------------------------------------
    # 重置逻辑（删除user_data全新启动）
    # ----------------------------------------------------------------------
    async def handle_reset(self, msg=None):
        """重置浏览器（删除所有user_data，清除Cookie，全新登录）"""
        logger.warning("=" * 60)
        logger.warning("⚠️  执行浏览器重置操作...")
        logger.warning("⚠️  这将删除所有浏览器数据（Cookie、缓存、登录状态）")
        logger.warning("=" * 60)
        
        try:
            result = await self.start_new_browser()
            logger.success("✅ 浏览器重置成功，已启动全新环境")
            
            # 发送状态更新（包含空cookie，表示已重置）
            if self.tcp_client:
                try:
                    await self.tcp_client.send_status(include_cookie=True)
                    logger.info("已通知控制端：浏览器已重置")
                except Exception as e:
                    logger.warning(f"通知控制端失败: {e}")
            
            return CommandRes.Reset().Success(msg="浏览器重置成功，请重新登录")
        except Exception as e:
            logger.error(f"浏览器重置失败: {e}")
            return CommandRes.Reset().Fail(msg=f"浏览器重置失败: {e}")

    # ----------------------------------------------------------------------
    # 登录逻辑
    # ----------------------------------------------------------------------
    async def handle_login(self, msg=None):
        """处理登录命令"""
        try:
            # 安全地获取消息参数
            if msg:
                try:
                    msg_dict = msg.to_dict() if hasattr(msg, 'to_dict') else msg
                    logger.info(f"收到登录命令, 参数: {msg_dict}")
                except Exception as e:
                    logger.warning(f"解析登录命令参数失败: {e}，使用原始msg")
                    msg_dict = msg if isinstance(msg, dict) else {}
            else:
                logger.warning("收到登录命令，但msg为None")
                msg_dict = {}
            
            # 检查浏览器是否已启动
            if not self.browser or not self.page:
                return CommandRes.Login().Fail(msg="浏览器未启动，请先启动浏览器")
            
            # ✅ 提前解析登录数据，以便在检查登录状态时使用
            login_data = msg_dict.get("data", {}) if isinstance(msg_dict, dict) else (msg.data if msg and hasattr(msg, 'data') else {})
            login_type = login_data.get("type") if login_data else None
            refresh = login_data.get("refresh", False) if login_data else False
            
            # ✅ 检查是否正在登录中（允许重置并开始新的登录流程）
            current_login_status = self.state.get("login_status")
            if current_login_status == "logging_in":
                # 检查是否是刷新二维码的请求
                if refresh:
                    logger.info("正在登录中，刷新二维码...")
                    # 继续执行登录流程（刷新二维码）
                else:
                    # 非刷新请求，可能是用户关闭对话框后重新打开
                    # 允许重置状态并开始新的登录流程
                    logger.info("⚠️  检测到新的登录请求，重置之前的登录状态并开始新的登录流程")
                    self.state.update(login_status="not_logged_in")
                    # 取消之前的二维码监控任务（如果存在）
                    if self._qrcode_watch_task and not self._qrcode_watch_task.done():
                        try:
                            self._qrcode_watch_task.cancel()
                            logger.debug("已取消之前的二维码监控任务")
                        except Exception as e:
                            logger.debug(f"取消二维码监控任务失败: {e}")
                    self._login_success_notified = False
                    self._qrcode_scan_detected = False
                    self._qrcode_redirect_url = None
            
            # ✅ 用户主动发起登录时，不检测登录状态（避免页面reload卡死）
            # 直接进入登录流程，让用户决定是否需要重新登录
            # 检测登录状态的逻辑移到 check_login_background 后台任务中
            logger.info("用户主动发起登录请求，直接进入登录流程...")
            
            # 如果未登录，处理登录请求
            
            if login_type == "qrcode":
                # 二维码登录
                return await self.handle_qrcode_login(refresh=refresh)
            elif login_type == "sms":
                # 短信登录
                return await self.handle_sms_login(msg)
            else:
                # 返回登录状态，需要前端选择登录方式
                return CommandRes.Login().Fail(
                    msg="未登录，请选择登录方式",
                    data={"logged_in": False, "login_methods": ["qrcode", "sms"]}
                )
        except Exception as e:
            logger.exception(f"处理登录命令异常: {e}")
            return CommandRes.Login().Fail(msg=f"登录命令处理失败: {e}")

    async def check_ban_status_from_api(self) -> bool:
        """
        通过API调用检测账号是否被封禁
        返回: True=封禁, False=未封禁
        
        检测方法：
        1. 调用搜索API（mtop.taobao.idlemtopsearch.pc.search）检测封禁状态
        2. 因为封禁通常只影响搜索/下单等操作，而不影响基础的用户信息API
        3. 如果搜索API返回风控错误且URL包含bixi.alicdn.com，说明被封禁
        """
        if not self.browser or not self.page:
            return False
        
        try:
            from modules.goofish_api import GoofishAPIClient, GoofishAPIs, ResponseCode
            
            logger.info("正在通过搜索API检测封禁状态...")
            api_client = GoofishAPIClient(self.state, self)
            try:
                # ✅ 使用搜索API检测封禁状态（因为封禁通常只影响搜索/下单操作）
                # 搜索API是实际工作API，能更准确地反映封禁状态
                search_result = await api_client.request(
                    GoofishAPIs.SEARCH,
                    {"pageNumber": 1, "keyword": "test", "rowsPerPage": 1},
                    force_check_login=True  # 强制检查登录，忽略封禁标记
                )
                
                # 检查是否返回风控错误（封禁）
                ret_codes = search_result.get("ret", [])
                if ret_codes and isinstance(ret_codes, list) and len(ret_codes) > 0:
                    # 检查是否为风控错误（RGV587）
                    has_rgv587 = any(
                        "RGV587" in str(c) or ResponseCode.RGV587_ERROR in str(c)
                        for c in (ret_codes if isinstance(ret_codes, list) else [ret_codes])
                    )
                    
                    if has_rgv587:
                        # 检查风控数据中的URL
                        risk_data = search_result.get("data", {})
                        risk_url = risk_data.get("url", "") or risk_data.get("h5url", "")
                        
                        # ✅ 判断是否为封禁：URL包含bixi.alicdn.com（这是封禁的明确标识）
                        if risk_url and "bixi.alicdn.com" in risk_url:
                            logger.warning(f"检测到封禁URL: {risk_url[:100]}...")
                            return True
                        else:
                            # 有RGV587但URL不包含bixi.alicdn.com，可能是临时风控（不是封禁）
                            logger.debug(f"检测到风控错误但非封禁URL: {risk_url[:100] if risk_url else '无URL'}...")
                            return False
                
                # 如果搜索API正常返回，说明未被封禁
                logger.debug("搜索API正常返回，账号未被封禁")
                return False
            finally:
                await api_client.close()
                
        except Exception as e:
            logger.error(f"检测封禁状态失败: {e}", exc_info=True)
            # 检测失败时，保守处理：返回False（未封禁），避免误判
            return False

    async def check_login(self, _retry_count: int = 0) -> bool:
        """调用接口检测登录状态并更新state
        
        Args:
            _retry_count: 内部重试计数器，防止无限循环（最多重试3次）
        """
        # ✅ 防止无限循环：如果重试次数超过3次，直接返回False
        if _retry_count >= 3:
            logger.error(f"❌ Token刷新重试已达到最大次数（{_retry_count}次），停止重试，标记为登录失效")
            self.state.mark_logout()
            return False
        
        # ✅ 检查浏览器是否可用
        if not self._is_browser_available():
            logger.debug("浏览器不可用，无法检测登录状态")
            self.state.update(login_status="not_logged_in")
            return False

        try:
            # ✅ 先获取cookie，检查是否有登录保持cookie（用于判断是否需要尝试恢复登录）
            # ✅ get_cookie_dict会自动检查浏览器状态并在需要时重启
            cookies_dict = await self.get_cookie_dict(auto_restart=True)
            has_login_keep_cookies = cookies_dict and any(
                cookie_name in cookies_dict 
                for cookie_name in ["havana_lgc2_77", "_hvn_lgc_", "havana_lgc_exp", "last_u_xianyu_xianyu_sdkSilent"]
            )
            
            # ✅ 如果账号已有封禁状态，检查是否需要重新检测
            if self.state.is_banned():
                if self.state.should_check_ban_status():
                    logger.debug("距离上次检测已满8小时，重新检测封禁状态...")
                    is_still_banned = await self.check_ban_status_from_api()
                    
                    self.state.update_ban_check_time()
                    
                    if is_still_banned:
                        logger.debug("账号仍处于封禁状态，将继续等待解封")
                        if not self.state.get("banned_at"):
                            self.state.set("banned_at", time.time())
                    else:
                        logger.success("账号已解封，清除封禁状态")
                        self.state.clear_banned()
                        
                        if self.tcp_client:
                            try:
                                asyncio.create_task(self.tcp_client.send_status(include_cookie=False))
                            except Exception as e:
                                logger.debug(f"发送解封状态失败: {e}")
                else:
                    remaining = self.state.get_ban_remaining_time()
                    hours = remaining / 3600
                    logger.debug(f"账号处于封禁状态，距离下次检测还有 {hours:.1f} 小时")
            
            # 检查是否有必要的登录cookie
            if not cookies_dict or '_m_h5_tk' not in cookies_dict:
                logger.debug("没有登录cookie，跳过登录检测")
                
                try:
                    screenshot_path = await self.capture_screenshot("check_login", "no_cookie")
                    if screenshot_path:
                        logger.debug(f"无cookie时已截图: {screenshot_path}")
                except:
                    pass
                
                self.state.mark_logout()
                return False
            
            # 更新cookie到state
            self.state.update(cookie=cookies_dict)
            
            # 使用 GoofishAPIClient 检测登录状态
            from modules.goofish_api import GoofishAPIClient, ResponseCode
            
            # 创建API客户端，直接通过 get_user_id 检测登录状态（一次API调用完成检测+获取用户ID）
            api_client = GoofishAPIClient(self.state, self)
            try:
                # ✅ 直接调用 get_user_id，既能检测登录又能获取用户ID
                # ✅ 即使账号标记为封禁状态，也强制调用API检查真实的登录状态（避免误判）
                # ✅ force_check_login=True 会绕过封禁状态检查，实际调用API来验证登录状态
                user_info = await api_client.get_user_id(force_check_login=True)
                
                # 检查返回码
                ret_codes = user_info.get("ret", [])
                if ret_codes and isinstance(ret_codes, list) and len(ret_codes) > 0:
                    ret_code = ret_codes[0]
                    # 判断返回码是否以 SUCCESS 开头
                    if isinstance(ret_code, str) and ret_code.startswith(ResponseCode.SUCCESS):
                        # 登录有效，获取用户ID
                        user_data = user_info.get("data", {})
                        user_id = user_data.get("userId")
                        
                        if user_id:
                            self.state.set("account", str(user_id))
                            self.state.set("account_user_id", str(user_id))
                            logger.info(f"登录有效，获取到userId: {user_id}")
                        else:
                            logger.warning("登录有效但未能获取userId，使用默认值")
                            account_id = self.state.get("account") or "User"
                            self.state.set("account", account_id)
                        
                        # ✅ 移除test搜索API验证：减少不必要的API调用，避免触发风控
                        # 原因：
                        # 1. get_user_id成功已经能说明登录有效
                        # 2. test搜索会增加API调用频率，可能触发风控
                        # 3. 如果Session真的过期，实际搜索时会自动触发恢复流程
                        logger.debug("✅ 登录验证通过（get_user_id成功），跳过test搜索验证以减少API调用")
                        
                        # 标记登录成功
                        self.state.finish_login(
                            success=True,
                            cookie=cookies_dict
                        )
                        
                        # 自动保存登录状态（延长Cookie有效期）
                        try:
                            await api_client.set_login_keep(True)
                            logger.info("已自动设置保存登录状态")
                        except Exception as e:
                            logger.warning(f"设置保存登录状态失败: {e}")
                        
                        # 登录成功后，始终检查一次搜索封禁状态
                        # 理由：封禁状态是动态的，需要实时检测
                        try:
                            logger.debug("登录成功，检查搜索封禁状态...")
                            from modules.goofish_api import GoofishAPIs
                            test_search_result = await api_client.request(
                                GoofishAPIs.SEARCH,
                                {"pageNumber": 1, "keyword": "test", "rowsPerPage": 1},
                                force_check_login=True
                            )
                            
                            ret_codes = test_search_result.get("ret", [])
                            risk_url = test_search_result.get("data", {}).get("url", "") or test_search_result.get("data", {}).get("h5url", "")
                            
                            # ✅ 判断是否为封禁：Code包含RGV587_ERROR且URL包含bixi.alicdn.com
                            has_rgv587 = any("RGV587" in str(c) or ResponseCode.RGV587_ERROR in str(c) for c in (ret_codes if isinstance(ret_codes, list) else [ret_codes]))
                            is_banned = has_rgv587 and "bixi.alicdn.com" in risk_url
                            
                            if is_banned:
                                logger.warning("⚠️  搜索API检测到账号处于封禁状态")
                                # 如果之前未被标记为封禁，现在标记为封禁
                                if not self.state.is_banned():
                                    self.state.set("banned_at", time.time())
                                    self.state.update(work_status="banned")
                                    logger.warning("账号被标记为封禁状态")
                            else:
                                # 未封禁或已解封
                                if self.state.is_banned():
                                    logger.success("✅ 账号已解封，清除封禁标记")
                                    self.state.clear_banned()
                                    
                                    if self.tcp_client:
                                        try:
                                            asyncio.create_task(self.tcp_client.send_status(include_cookie=False))
                                        except Exception as e:
                                            logger.debug(f"发送解封状态失败: {e}")
                                else:
                                    logger.debug("登录成功，账号未被封禁")
                        except Exception as e:
                            logger.debug(f"调用搜索API判断封禁状态失败: {e}")
                        
                        return True
                
                # 登录失效或其他错误
                logger.debug(f"登录检测失败，返回码: {ret_codes}")
                
                # ✅ 检查是否是风控（RGV587）
                has_risk_control = any(
                    "RGV587" in str(code) or ResponseCode.RGV587_ERROR in str(code)
                    for code in (ret_codes if isinstance(ret_codes, list) else [ret_codes])
                )
                
                if has_risk_control:
                    # ✅ 风控状态：账号仍处于登录状态，但触发了风控验证（人机验证）
                    logger.debug("检测到风控验证（RGV587），账号仍处于登录状态")
                    
                    # ✅ 保持登录状态，不调用 mark_logout()
                    # ✅ 风控验证已在 goofish_api 的 _handle_response 中处理
                    try:
                        page_title = await self.page.title()
                        if "闲鱼" in page_title and not ("登录" in page_title or "login" in page_title.lower()):
                            logger.debug(f"页面标题显示账号已登录: {page_title}")
                            return True
                    except:
                        pass
                    
                    # ✅ 如果无法确认登录状态，保持登录状态但不标记为封禁（风控可以处理）
                    return True
                
                # ✅ 检查是否是会话冲突（Session过期可能是被其他设备登录踢掉）
                session_expired = any(
                    "FAIL_SYS_SESSION_EXPIRED" in str(code) or "Session过期" in str(code)
                    for code in (ret_codes if isinstance(ret_codes, list) else [ret_codes])
                )
                
                if session_expired:
                    # ✅ 优化：根据网络请求分析，Session过期时先尝试刷新token（删除token后请求API会自动刷新）
                    logger.info("检测到Session过期，先尝试刷新token（删除token后请求API）...")
                    try:
                        # 检查是否有 Session Cookie（如 cookie2, unb 等）
                        cookies = self.state.get("cookie", {})
                        has_session_cookies = any(key in cookies for key in ['cookie2', 'unb', '_tb_token_'])
                        
                        if has_session_cookies:
                            logger.info("检测到Session Cookie存在，尝试通过API刷新token...")
                            logger.info("  方法：删除cookie中的_m_h5_tk，使用空token请求API刷新token")
                            
                            # 尝试刷新 token（_refresh_token 会删除token后请求首页API）
                            token_result = await api_client._refresh_token()
                            
                            if token_result.get("success"):
                                logger.success(f"Token刷新成功（第{_retry_count + 1}次尝试），重新检测登录状态...")
                                # 刷新成功后，重新检测登录状态（使用新的token）
                                # 传递递增的重试计数器，防止无限循环
                                retry_login = await self.check_login(_retry_count=_retry_count + 1)
                                if retry_login:
                                    logger.success("Token刷新后登录验证成功，无需浏览器操作")
                                    return True
                                else:
                                    logger.warning(f"Token刷新后登录验证仍失败（已重试{_retry_count + 1}次），可能Session已完全过期")
                            else:
                                logger.warning(f"Token刷新失败: {token_result.get('msg', '未知错误')}")
                                # 如果返回非法请求，可能是时间戳或签名问题
                                if token_result.get("illegal_access"):
                                    logger.error("Token刷新返回非法请求，可能是时间戳或签名计算错误")
                        else:
                            logger.debug("未检测到Session Cookie，跳过token刷新")
                    except Exception as e:
                        logger.debug(f"尝试刷新token时出错: {e}")
                    
                    # ✅ 如果token刷新失败或没有Session Cookie，尝试使用浏览器方式
                    if has_login_keep_cookies and self._is_browser_available() and self.page:
                        logger.info("Token刷新失败，尝试通过'快速进入'按钮重新登录...")
                        
                        # 尝试点击"快速进入"按钮恢复登录
                        try:
                            await self.page.reload(wait_until="domcontentloaded", timeout=30000)
                            await asyncio.sleep(3)

                            login_box = await self.page.query_selector("#alibaba-login-box")

                            if login_box:
                                clicked = False
                                logger.info("✅ 检测到登录框，尝试点击'快速进入'按钮...")
                                frame = await login_box.content_frame()
                                if frame:
                                    # 检测 has-login 页面（快速进入）
                                    has_login_div = await frame.query_selector("body .has-login")
                                    if has_login_div:
                                        quick_button = await frame.query_selector(
                                            "button.fm-button.fm-submit:has-text('快速进入')")
                                        if quick_button:
                                            logger.info("检测到快速进入按钮，执行点击")
                                            await quick_button.click(timeout=5000)
                                            clicked = True
                                            
                                            # ✅ 点击后等待页面跳转和cookie刷新
                                            logger.info("✅ 已点击'快速进入'按钮，等待页面跳转和cookie刷新...")
                                            
                                            # 步骤1：等待页面跳转完成
                                            try:
                                                await asyncio.sleep(3)
                                                for i in range(7):
                                                    await asyncio.sleep(1)
                                                    current_url = self.page.url
                                                    page_title = await self.page.title()
                                                    if "login" not in current_url.lower() and "登录" not in page_title:
                                                        logger.debug(f"✅ 页面已跳转: {current_url}")
                                                        break
                                            except Exception as e:
                                                logger.debug(f"等待页面跳转时出错: {e}")
                                            
                                            # 步骤2：等待并获取最新cookie
                                            await asyncio.sleep(3)
                                            cookies_dict = await self.get_cookie_dict(auto_restart=False)
                                            logger.debug(f"已获取最新cookie，包含cookie2: {'cookie2' in cookies_dict}")
                                            
                                            # 步骤3：使用has_login()接口验证登录
                                            from modules.goofish_api import GoofishAPIClient
                                            temp_api_client = GoofishAPIClient(self.state, self)
                                            try:
                                                login_check = await temp_api_client.has_login()
                                                if login_check.get("is_logged_in"):
                                                    logger.success("✅ 通过silentHasLogin验证登录成功")
                                                    
                                                    # 步骤4：再次获取cookie（has_login会刷新cookie2等）
                                                    await asyncio.sleep(1)
                                                    final_cookies = await self.get_cookie_dict(auto_restart=False)
                                                    self.state.update(cookie=final_cookies)
                                                    
                                                    # 步骤5：使用get_user_id获取用户ID
                                                    user_info = await temp_api_client.get_user_id(force_check_login=True)
                                                    user_data = user_info.get("data", {})
                                                    user_id = user_data.get("userId")
                                                    if user_id:
                                                        self.state.set("account", str(user_id))
                                                        self.state.set("account_user_id", str(user_id))
                                                        logger.success(f"✅ 通过'快速进入'按钮登录成功: {user_id}")
                                                    
                                                    # ✅ 标记登录成功
                                                    self.state.finish_login(success=True, cookie=final_cookies)
                                                    return True
                                                else:
                                                    logger.warning(f"⚠️ silentHasLogin验证失败: {login_check.get('error')}")
                                            except Exception as e:
                                                logger.warning(f"验证登录时出错: {e}")
                                            finally:
                                                await temp_api_client.close()
                                            
                                            logger.warning("⚠️ 点击'快速进入'按钮后仍未登录成功")
                            else:
                                logger.debug("未检测到登录框")
                        except Exception as e:
                            logger.debug(f"尝试恢复登录时出错: {e}")
                    elif has_login_keep_cookies and not self._is_browser_available():
                        logger.debug("浏览器不可用，无法尝试'快速进入'登录")
            finally:
                # 确保关闭API客户端的session
                await api_client.close()
            
        except Exception as e:
            logger.warning(f"登录检测异常: {e}")
            
            # ✅ 异常时截图
            try:
                screenshot_path = await self.capture_screenshot("check_login", f"exception_{type(e).__name__}")
                if screenshot_path:
                    logger.info(f"📸 登录检测异常时已截图: {screenshot_path}")
            except:
                pass

        # 登录失效
        self.state.mark_logout()
        logger.warning("登录状态失效，请重新登录")
        
        # ✅ 登录失效时截图，查看页面状态
        try:
            screenshot_path = await self.capture_screenshot("check_login", "login_expired")
            if screenshot_path:
                logger.info(f"📸 登录失效时已截图: {screenshot_path}")
                
                # 获取页面信息帮助诊断
                try:
                    current_url = self.page.url
                    page_title = await self.page.title()
                    logger.info(f"登录失效时页面URL: {current_url}")
                    logger.info(f"登录失效时页面标题: {page_title}")
                    
                    # 检查页面是否有登录相关元素
                    try:
                        # 检查是否有登录弹窗
                        login_box = await self.page.query_selector("#alibaba-login-box, .login-box, [class*='login']")
                        if login_box:
                            logger.info("⚠️ 页面存在登录相关元素，可能正在等待登录")
                        else:
                            logger.info("⚠️ 页面无登录相关元素，可能已跳转到登录页或首页")
                    except:
                        pass
                except Exception as e:
                    logger.warning(f"获取页面信息失败: {e}")
        except Exception as e:
            logger.warning(f"登录失效时截图失败: {e}")
        
        return False
    
    async def refresh_and_check_login(self) -> bool:
        """
        当API返回登录过期时，尝试刷新页面重新登录
        
        工作流程：
        1. 检查是否距离上次恢复时间过短（限制恢复频率，至少5分钟）
        2. 检查页面是否响应（快速健康检查）
        3. 如果页面卡死，重启浏览器
        4. 访问personal页面（模拟正常用户访问，触发自动登录）
        5. 等待自动登录插件执行
        6. 重新检测登录状态并更新Cookie
        7. 如果成功，返回True并更新Cookie
        8. 如果失败，返回False（确实已过期）
        """
        import time
        
        # ✅ 限制Session恢复频率：至少间隔5分钟，避免频繁恢复触发风控
        last_refresh_time = self.state.get("last_session_refresh_time", 0)
        current_time = time.time()
        time_since_last_refresh = current_time - last_refresh_time
        min_refresh_interval = 300  # 5分钟
        
        if time_since_last_refresh < min_refresh_interval:
            remaining_seconds = int(min_refresh_interval - time_since_last_refresh)
            logger.warning(f"⚠️ 距离上次Session恢复仅{int(time_since_last_refresh)}秒，跳过本次恢复（至少需间隔{min_refresh_interval}秒，还需等待{remaining_seconds}秒）")
            logger.warning("⚠️ 如果Session确实过期，将在下次搜索时自动恢复")
            return False
        
        # ✅ 检查浏览器是否可用，如果不可用但需要cookie，尝试重启浏览器
        if not self._is_browser_available():
            logger.warning("浏览器不可用，尝试重启浏览器...")
            browser_available = await self._ensure_browser_available(need_cookie=True)
            if not browser_available:
                logger.error("❌ 浏览器不可用且无法重启，无法刷新登录")
                return False
        
        try:
            logger.info("检测到登录过期，尝试刷新页面重新登录...")
            
            # ✅ 先检查页面是否还活着
            try:
                await asyncio.wait_for(self.page.title(), timeout=3)
            except (asyncio.TimeoutError, Exception) as page_error:
                logger.error(f"❌ 页面已卡死或无响应: {page_error}")
                logger.info("🔄 尝试重启浏览器恢复...")
                
                # 重启浏览器
                try:
                    await self.stop_browser()
                    await asyncio.sleep(2)
                    await self.start_browser()
                    await asyncio.sleep(3)
                    logger.success("✅ 浏览器已重启")
                except Exception as restart_error:
                    logger.error(f"❌ 浏览器重启失败: {restart_error}")
                    return False
            
            # ✅ 访问personal页面（模拟正常用户访问，触发自动登录）
            logger.info("🔄 访问personal页面，触发浏览器自动登录...")
            try:
                await self.page.goto("https://www.goofish.com/personal", timeout=10000, wait_until="domcontentloaded")
            except Exception as goto_error:
                logger.warning(f"访问personal页面失败: {goto_error}，尝试直接访问首页")
                await self.page.goto("https://www.goofish.com", timeout=10000, wait_until="domcontentloaded")
            logger.info("已访问personal页面")
            
            # ✅ 等待浏览器完成自动登录和cookie刷新
            await asyncio.sleep(5)
            
            # ✅ 重新同步cookie（浏览器可能已自动更新）
            await self.get_cookie_dict()
            
            # ✅ 等待一下，让浏览器完成所有API请求（token可能通过Set-Cookie返回）
            await asyncio.sleep(2)
            
            # ✅ 再次同步cookie（确保获取到最新的token）
            await self.get_cookie_dict()
            
            # 重新检测登录状态
            is_logged_in = await self.check_login()
            
            if is_logged_in:
                # ✅ 更新最后恢复时间
                self.state.set("last_session_refresh_time", current_time)
                
                logger.success("✅ 自动刷新登录成功！Cookie已更新")
                
                # ✅ 关键修复：确保token已同步到API客户端
                # 浏览器刷新后，token可能通过API响应的Set-Cookie返回，需要确保同步到API session
                api_client = None
                try:
                    from modules.goofish_api import GoofishAPIClient
                    api_client = GoofishAPIClient(self.state, self)
                    # 调用get_session会从浏览器同步cookie到session，确保token已更新
                    await api_client.get_session()
                    
                    # ✅ 如果state中还是没有token，主动刷新一次token
                    current_cookies = self.state.get("cookie", {})
                    # ✅ 检查token是否存在且不为空字符串（不能为None）
                    current_token_value = current_cookies.get("_m_h5_tk", "")
                    if not current_token_value or current_token_value.strip() == "":
                        logger.warning("⚠️ 浏览器cookie中还没有token，尝试主动刷新token...")
                        token_result = await api_client._refresh_token()
                        if token_result.get("success"):
                            logger.success("✅ 主动刷新token成功")
                        else:
                            logger.warning(f"⚠️ 主动刷新token失败: {token_result.get('msg')}")
                    else:
                        logger.debug(f"✅ Token已存在: {current_token_value[:30]}...")
                except Exception as e:
                    logger.warning(f"同步token到API session失败: {e}")
                finally:
                    # ✅ 确保临时创建的api_client被正确关闭，避免"Unclosed client session"警告
                    if api_client:
                        try:
                            await api_client.close()
                        except Exception as e:
                            logger.debug(f"关闭api_client失败（不影响功能）: {e}")
                
                # 主动发送cookie更新到控制端
                if self.tcp_client:
                    try:
                        await self.tcp_client.send_cookie_update()
                        logger.info("已通知控制端：登录状态已恢复")
                    except Exception as e:
                        logger.warning(f"通知控制端失败: {e}")
                
                return True
            else:
                logger.warning("⚠️  刷新后仍未登录，Cookie确实已失效")
                
                # ✅ 截图查看为什么没有自动登录
                try:
                    screenshot_path = await self.capture_screenshot("refresh_login", "login_expired_after_refresh")
                    if screenshot_path:
                        logger.info(f"📸 已截图保存登录过期状态: {screenshot_path}")
                        
                        # 尝试获取页面URL和标题，帮助诊断
                        try:
                            current_url = self.page.url
                            page_title = await self.page.title()
                            logger.info(f"当前页面URL: {current_url}")
                            logger.info(f"当前页面标题: {page_title}")
                            
                            # 检查是否有登录弹窗或iframe
                            try:
                                login_iframe = await self.page.query_selector("#alibaba-login-box iframe, iframe[id*='login']")
                                if login_iframe:
                                    logger.info("⚠️ 检测到登录iframe存在，但自动登录插件可能未触发")
                                else:
                                    logger.info("⚠️ 未检测到登录iframe，可能是cookie完全失效")
                            except:
                                pass
                        except Exception as e:
                            logger.warning(f"获取页面信息失败: {e}")
                except Exception as e:
                    logger.warning(f"截图失败: {e}")
                
                return False
        
        except Exception as e:
            logger.error(f"刷新登录失败: {e}")
            
            # ✅ 异常时也截图，查看页面状态
            try:
                screenshot_path = await self.capture_screenshot("refresh_login", f"exception_{type(e).__name__}")
                if screenshot_path:
                    logger.info(f"📸 异常时已截图: {screenshot_path}")
            except:
                pass
            
            return False

    # ----------------------------------------------------------------------
    # Cookie
    # ----------------------------------------------------------------------
    async def _sync_login_keep_cookies_to_browser(self):
        """
        将state中的登录保持相关cookie同步到浏览器
        这些cookie可能在state中但不在浏览器的持久存储中，导致自动登录失败
        """
        try:
            if not self.browser:
                return
            
            # 登录保持相关cookie列表
            login_keep_cookies = [
                "havana_lgc2_77",
                "_hvn_lgc_",
                "havana_lgc_exp",
                "last_u_xianyu_xianyu_sdkSilent",
                "last_cc",  # 也可能是必需的
            ]
            
            # 从state中获取cookie
            state_cookies = self.state.get("cookie", {})
            if not state_cookies:
                logger.debug("state中无cookie，跳过登录保持cookie同步")
                return
            
            # 检查是否有登录保持相关cookie
            cookies_to_sync = {}
            for cookie_name in login_keep_cookies:
                if cookie_name in state_cookies:
                    cookies_to_sync[cookie_name] = state_cookies[cookie_name]
            
            if not cookies_to_sync:
                logger.debug("state中无登录保持相关cookie，跳过同步")
                return
            
            # 获取domain（默认使用.goofish.com，兼容性最好）
            domain = ".goofish.com"  # 使用点开头的domain可以匹配所有子域名
            
            # 尝试从当前页面获取domain
            if self.page and not self.page.is_closed():
                try:
                    current_url = self.page.url
                    from urllib.parse import urlparse
                    parsed = urlparse(current_url)
                    if parsed.netloc:
                        # 移除端口号
                        netloc = parsed.netloc.split(':')[0]
                        # 对于goofish.com，使用.goofish.com
                        if 'goofish.com' in netloc:
                            domain = ".goofish.com"
                        else:
                            domain = netloc
                except Exception as e:
                    logger.debug(f"获取页面URL失败: {e}，使用默认domain: {domain}")
            
            # 构建cookie列表
            cookies_list = []
            for cookie_name, cookie_value in cookies_to_sync.items():
                cookies_list.append({
                    "name": cookie_name,
                    "value": cookie_value,
                    "domain": domain,
                    "path": "/"
                })
            
            # 添加到浏览器
            await self.browser.add_cookies(cookies_list)
            logger.info(f"✅ 已同步 {len(cookies_list)} 个登录保持相关cookie到浏览器: {', '.join(cookies_to_sync.keys())}")
            
        except Exception as e:
            logger.warning(f"同步登录保持cookie到浏览器失败: {e}（不影响使用）")
    
    def _is_browser_available(self) -> bool:
        """
        检查浏览器是否可用
        Returns:
            True: 浏览器可用，False: 浏览器不可用
        """
        try:
            # 检查浏览器状态（通过state）- 先检查状态，避免访问已关闭的浏览器对象
            browser_status = self.state.get("browser_status")
            if browser_status not in ["running", "starting"]:
                return False
            
            # 检查浏览器对象是否存在
            if not self.browser:
                return False
            
            # 检查页面是否存在
            if not self.page:
                return False
            
            # 检查浏览器状态（通过检查页面是否关闭）
            # ⚠️ 注意：如果浏览器已关闭，调用is_closed()可能抛出异常，需要捕获
            try:
                if self.page.is_closed():
                    return False
            except Exception as e:
                # 如果检查页面状态时出错，说明浏览器已关闭或异常
                logger.debug(f"检查页面状态时出错（浏览器可能已关闭）: {e}")
                return False
            
            return True
        except Exception as e:
            logger.debug(f"检查浏览器状态时出错: {e}")
            return False
    
    async def _ensure_browser_available(self, need_cookie: bool = False) -> bool:
        """
        确保浏览器可用，如果不可用且need_cookie=True，则尝试重启浏览器
        
        Args:
            need_cookie: 是否需要cookie（如果需要cookie但浏览器不可用，会尝试重启）
        
        Returns:
            True: 浏览器可用，False: 浏览器不可用且无法恢复
        """
        if self._is_browser_available():
            return True
        
        # 浏览器不可用
        browser_status = self.state.get("browser_status")
        logger.warning(f"⚠️ 浏览器不可用 (browser_status={browser_status})")
        
        # 如果不需要cookie，直接返回False
        if not need_cookie:
            logger.debug("不需要cookie，不重启浏览器")
            return False
        
        # 如果需要cookie但浏览器不可用，尝试重启浏览器
        logger.info("🔄 需要cookie但浏览器不可用，尝试重启浏览器...")
        try:
            # 检查是否正在启动中
            if self.state.get("browser_status") == "starting":
                logger.info("浏览器正在启动中，等待...")
                # 等待最多5秒
                for _ in range(10):
                    await asyncio.sleep(0.5)
                    if self._is_browser_available():
                        logger.info("✅ 浏览器已启动")
                        return True
                logger.warning("⚠️ 等待浏览器启动超时")
                return False
            
            # 尝试启动浏览器
            result = await self.start_browser()
            if result.get("success"):
                logger.success("✅ 浏览器重启成功")
                return True
            else:
                logger.error(f"❌ 浏览器重启失败: {result.get('msg', '未知错误')}")
                return False
        except Exception as e:
            logger.error(f"❌ 重启浏览器时出错: {e}")
            return False
    
    async def get_cookie_dict(self, auto_restart: bool = True) -> dict:
        """
        提取当前上下文 Cookie 并更新 state
        
        Args:
            auto_restart: 如果浏览器不可用，是否自动重启（默认True）
        
        Returns:
            cookie字典，如果浏览器不可用且无法恢复，返回空字典
        """
        # ✅ 检查浏览器是否可用
        if not self._is_browser_available():
            if auto_restart:
                # 尝试重启浏览器
                browser_available = await self._ensure_browser_available(need_cookie=True)
                if not browser_available:
                    logger.warning("⚠️ 浏览器不可用且无法重启，无法获取cookie")
                    return {}
            else:
                logger.warning("⚠️ 浏览器不可用，无法获取cookie")
                return {}
        
        try:
            cookies = await self.browser.cookies()
            cookies_dict = {c["name"]: c["value"] for c in cookies}
            cookies_str = json.dumps(cookies_dict)
            logger.info(f'从浏览器获取到cookie:{cookies_str}')
            self.state.set("cookie", cookies_dict)
            return cookies_dict
        except Exception as e:
            # 检查是否是浏览器关闭导致的错误
            error_str = str(e).lower()
            if "closed" in error_str or "target" in error_str or "connection" in error_str:
                logger.warning(f"浏览器已关闭: {e}")
                # 触发浏览器关闭处理（但不阻塞当前流程）
                asyncio.create_task(self._on_browser_closed())
                
                # ✅ 如果auto_restart=True且需要cookie，尝试重启浏览器
                if auto_restart:
                    logger.info("🔄 浏览器已关闭，尝试重启浏览器获取cookie...")
                    browser_available = await self._ensure_browser_available(need_cookie=True)
                    if browser_available:
                        # 重启成功后，再次尝试获取cookie
                        try:
                            cookies = await self.browser.cookies()
                            cookies_dict = {c["name"]: c["value"] for c in cookies}
                            cookies_str = json.dumps(cookies_dict)
                            self.state.set("cookie", cookies_dict)
                            logger.success(f"✅ 浏览器重启后成功获取cookie:{cookies_str}")
                            return cookies_dict
                        except Exception as e2:
                            logger.error(f"❌ 浏览器重启后获取cookie失败: {e2}")
                            return {}
                    else:
                        logger.error("❌ 浏览器重启失败，无法获取cookie")
                        return {}
            else:
                logger.warning(f"获取 Cookie 失败: {e}")
            return {}

    # ----------------------------------------------------------------------
    # 事件处理接口（供TCP调用）
    # ----------------------------------------------------------------------
    async def handle_start(self, msg=None):
        return await self.start_browser()

    async def handle_restart(self, msg=None):
        """全新启动"""
        return await self.start_new_browser()

    async def handle_heartbeat(self, msg=None):
        self.state.mark_heartbeat()
        return CommandRes.Heartbeat().Success(msg="心跳正常")

    # ----------------------------------------------------------------------
    # 二维码登录
    # ----------------------------------------------------------------------
    async def handle_qrcode_login(self, refresh=False):
        """处理二维码登录
        
        Args:
            refresh: 是否刷新二维码（二维码过期时）
        """
        try:
            # 检查浏览器是否已启动
            if not self.browser or not self.page:
                logger.error("浏览器未启动，无法进行二维码登录")
                return CommandRes.Login().Fail(msg="浏览器未启动，请先启动浏览器")
            
            if not refresh:
                # 重置登录通知标记和扫码检测标记（开始新的登录流程）
                self._login_success_notified = False
                self._qrcode_scan_detected = False
                self._qrcode_redirect_url = None
                self.state.start_login(account_name="QRCode")
                
                # ⚠️ 先检查是否已经登录
                logger.debug("检查当前登录状态...")
                is_logged = await self.check_login()
                if is_logged:
                    logger.success(f"✅ 账号已登录: {self.state.get('account')}，无需扫码")
                    return CommandRes.Login().Success(
                        msg="账号已登录",
                        data={"account": self.state.get('account'), "type": "already_logged"}
                    )
                
                # ⚠️ 检查当前页面URL
                current_url = self.page.url
                logger.info(f"当前页面: {current_url}")
                
                # 未登录，需要显示登录框，必须导航到personal页面
                logger.info("未登录，导航到个人中心触发登录框...")
                try:
                    # 增加超时时间，降级 wait_until（服务器网络可能慢）
                    await self.page.goto(
                        "https://www.goofish.com/personal", 
                        timeout=60000,  # 60秒
                        wait_until="domcontentloaded"  # 降级：不等待完全加载
                    )
                    await asyncio.sleep(3)
                    logger.info("页面已加载")
                except Exception as e:
                    logger.warning(f"导航失败: {e}，尝试继续")
                
                # 再次检查浏览器状态（防止在页面加载过程中被意外关闭）
                if not self.browser or not self.page:
                    logger.error("浏览器在页面加载过程中被关闭")
                    self.state.set_login_error("浏览器已关闭")
                    return CommandRes.Login().Fail(msg="浏览器已关闭，请重新启动")
                
                # 等待登录iframe出现（支持多个可能的选择器）
                login_box_selectors = [
                    "#alibaba-login-box",  # 原始选择器
                    "iframe[id*='login']",  # 包含login的iframe
                    "iframe[src*='login']",  # src包含login的iframe
                    ".login-box",  # class包含login-box
                ]
                
                login_box_found = False
                last_error = None
                
                for selector in login_box_selectors:
                    try:
                        logger.debug(f"尝试选择器: {selector}")
                        await self.page.wait_for_selector(selector, timeout=5000)
                        logger.info(f"✅ 登录框已加载（选择器: {selector}）")
                        login_box_found = True
                        break
                    except Exception as e:
                        last_error = e
                        logger.debug(f"选择器 {selector} 未找到")
                        continue
                
                if not login_box_found:
                    # 登录框没出现，尝试刷新页面触发
                    logger.warning("登录框未出现，尝试刷新页面触发...")
                    try:
                        await self.page.reload(
                            wait_until="domcontentloaded",  # 降级
                            timeout=30000  # 30秒
                        )
                        await asyncio.sleep(3)
                        
                        # 再次尝试查找登录框
                        for selector in login_box_selectors:
                            try:
                                await self.page.wait_for_selector(selector, timeout=5000)
                                logger.info(f"✅ 刷新后登录框已出现（选择器: {selector}）")
                                login_box_found = True
                                break
                            except:
                                continue
                    except Exception as e:
                        logger.error(f"刷新页面失败: {e}")
                
                if not login_box_found:
                    logger.error(f"所有登录框选择器都未找到，最后错误: {last_error}")
                    # 📸 超时时自动截图，方便排查问题
                    try:
                        await self.capture_screenshot("login", "wait_login_box_timeout")
                    except:
                        logger.warning("截图失败，跳过")
                    
                    self.state.set_login_error("登录框加载超时")
                    
                    # ⚠️ 重要：失败时必须返回，让前端关闭等待窗口
                    return CommandRes.Login().Fail(
                        msg="登录框加载超时，请尝试'重置'浏览器或检查网络",
                        data={"error_type": "login_box_timeout"}
                    )
                
                # 额外等待确保iframe内容加载完成
                await asyncio.sleep(2)
            else:
                # 刷新二维码
                logger.info("刷新二维码...")
                await self._refresh_qrcode()
            
            # 获取二维码
            logger.info("正在获取二维码...")
            qr_base64 = await self.get_qrcode_base64()
            if not qr_base64:
                self.state.set_login_error("获取二维码失败")
                return CommandRes.Login().Fail(msg="获取二维码失败，请重试")
            
            logger.info("二维码获取成功，等待扫码...")
            
            # ✅ 启动异步扫码检测任务（确保只有一个任务在运行）
            # 如果之前的任务还在运行，先取消它
            if self._qrcode_watch_task and not self._qrcode_watch_task.done():
                try:
                    self._qrcode_watch_task.cancel()
                    logger.debug("已取消之前的二维码监控任务")
                except Exception as e:
                    logger.debug(f"取消二维码监控任务失败: {e}")
            
            # 重置所有标记
            self._login_success_notified = False  # 重置通知标记
            self._qrcode_scan_detected = False  # 重置扫码检测标记
            self._qrcode_redirect_url = None  # 重置跳转URL
            
            # 创建新的监控任务
            self._qrcode_watch_task = asyncio.create_task(self._watch_qrcode_scan())
            logger.debug("已创建新的二维码监控任务")
            
            # 立即返回二维码给控制端展示（异步模式）
            return CommandRes.Login().Success(
                msg="请扫码登录",
                data={
                    "type": "qrcode",
                    "qrcode": qr_base64,
                    "wait_scan": True,
                    "expires_in": 600  # 10分钟过期
                }
            )
            
        except Exception as e:
            logger.error(f"二维码登录失败: {e}")
            self.state.set_login_error(str(e))
            return CommandRes.Login().Fail(msg=f"二维码登录失败: {e}")
    
    async def _refresh_qrcode(self):
        """刷新二维码"""
        try:
            # 获取iframe
            iframe_element = await self.page.query_selector("#alibaba-login-box")
            if not iframe_element:
                logger.warning("未找到登录iframe，重新访问登录页")
                await self.page.goto("https://www.goofish.com/personal", timeout=15000)
                await asyncio.sleep(2)
                iframe_element = await self.page.query_selector("#alibaba-login-box")
            
            iframe = await iframe_element.content_frame()
            
            # 查找刷新按钮
            refresh_btn = await iframe.query_selector(".qrcode-error button.refresh")
            if refresh_btn:
                await refresh_btn.click()
                await asyncio.sleep(1)
                logger.info("二维码刷新成功")
            else:
                logger.warning("未找到刷新按钮，二维码可能未过期")
        except Exception as e:
            logger.error(f"刷新二维码失败: {e}")
    
    async def _setup_qrcode_response_listener(self):
        """设置二维码检测接口的响应监听（监听iframe内的JS请求）"""
        self._qrcode_scan_detected = False  # 标记是否检测到扫码成功
        
        async def handle_response(response):
            """处理响应，检测二维码扫描成功"""
            try:
                # 只监听二维码检测接口
                if "/newlogin/qrcode/query.do" in response.url:
                    # 读取响应内容
                    body = await response.text()
                    logger.debug(f"检测到二维码查询请求，响应: {body[:200] if body else 'empty'}...")
                    
                    # 解析二维码状态
                    if body:
                        try:
                            import json
                            data = json.loads(body)
                            qr_status = data.get('content', {}).get('data', {}).get('qrCodeStatus')
                            
                            if qr_status:
                                logger.debug(f"二维码状态: {qr_status}")
                                
                                # 向前端发送状态更新
                                if self.tcp_client:
                                    status_msg = {
                                        "NEW": "等待扫码",
                                        "SCANED": "已扫描，等待确认",
                                        "CONFIRMED": "已确认，处理中..."
                                    }.get(qr_status, "处理中...")
                                    
                                    # 发送状态通知
                                    asyncio.create_task(self.tcp_client.send(
                                        CommandRes.Login().Success(
                                            msg=status_msg,
                                            data={
                                                "type": "qrcode_status",
                                                "status": qr_status
                                            }
                                        )
                                    ))
                                
                                # 检测CONFIRMED状态
                                if qr_status == "CONFIRMED":
                                    logger.success("🎉 检测到扫码确认！（监听iframe请求）")
                                    self._qrcode_scan_detected = True
                                    logger.debug(f"✅ 已设置扫码检测标志: _qrcode_scan_detected={self._qrcode_scan_detected}")
                                    
                                    # 提取iframeRedirectUrl
                                    redirect_url = data.get('content', {}).get('data', {}).get('iframeRedirectUrl')
                                    if redirect_url:
                                        logger.info(f"检测到登录跳转URL: {redirect_url[:100]}...")
                                        self._qrcode_redirect_url = redirect_url
                        except Exception as e:
                            logger.warning(f"解析二维码状态失败: {e}")
            except Exception as e:
                logger.debug(f"处理响应失败: {e}")
        
        # 移除旧的监听器（如果有）
        if hasattr(self, '_qrcode_response_handler'):
            try:
                self.page.remove_listener("response", self._qrcode_response_handler)
                logger.debug("已移除旧的二维码响应监听器")
            except Exception as e:
                logger.debug(f"移除旧监听器失败（可能不存在）: {e}")
        
        # 添加新的监听器
        self._qrcode_response_handler = handle_response
        self.page.on("response", handle_response)
        logger.debug("已设置二维码响应监听器")
    
    async def _remove_qrcode_response_listener(self):
        """移除二维码响应监听器"""
        try:
            if hasattr(self, '_qrcode_response_handler'):
                self.page.remove_listener("response", self._qrcode_response_handler)
                logger.debug("已移除二维码响应监听器")
        except Exception as e:
            logger.debug(f"移除响应监听器失败: {e}")
    
    async def _check_face_verification(self):
        """检测是否需要人脸识别，如果需要则提取二维码"""
        try:
            # 获取登录iframe
            login_iframe_frames = self.page.frames
            login_iframe = None
            for frame in login_iframe_frames:
                if "alibaba-login-box" in frame.name or "login" in frame.url.lower():
                    login_iframe = frame
                    break
            
            if not login_iframe:
                logger.debug("未找到登录iframe")
                return None
            
            # 获取iframe内容（不是主页面内容）
            try:
                iframe_content = await login_iframe.content()
            except Exception as e:
                logger.debug(f"获取iframe内容失败: {e}")
                return None
            
            # 检测人脸识别关键字
            face_keywords = ["拍摄脸部", "人脸识别", "人脸验证", "刷脸", "面部识别", "扫码验证身份"]
            
            has_face_verify = False
            matched_keyword = None
            for keyword in face_keywords:
                if keyword in iframe_content:
                    logger.info(f"✅ 检测到人脸识别关键字: {keyword}")
                    has_face_verify = True
                    matched_keyword = keyword
                    break
            
            if not has_face_verify:
                logger.debug("未检测到人脸识别关键字")
                return None
            
            # 查找人脸识别二维码的canvas（在iframe内）
            try:
                logger.info("正在查找人脸识别二维码...")
                # 等待canvas出现
                await asyncio.sleep(2)
                
                # 尝试多种选择器（在iframe内查找）
                qrcode_selectors = [
                    "canvas",  # 通用canvas
                    "#qrcode canvas",
                    ".qrcode-canvas",
                    "[class*='qrcode'] canvas",
                    "[id*='qrcode'] canvas"
                ]
                
                qrcode_element = None
                for selector in qrcode_selectors:
                    try:
                        # 在iframe内查找
                        elements = await login_iframe.query_selector_all(selector)
                        if elements:
                            qrcode_element = elements[0]
                            logger.info(f"✅ 找到人脸识别二维码canvas（选择器: {selector}）")
                            break
                    except Exception as e:
                        logger.debug(f"选择器 {selector} 失败: {e}")
                        continue
                
                if not qrcode_element:
                    logger.warning("⚠️ 未找到人脸识别二维码canvas，尝试截取整个iframe")
                    # 如果找不到canvas，截取整个iframe
                    try:
                        screenshot_bytes = await login_iframe.screenshot()
                        if screenshot_bytes:
                            import base64
                            screenshot_base64 = base64.b64encode(screenshot_bytes).decode('utf-8')
                            logger.success(f"✅ 成功截取iframe (大小: {len(screenshot_bytes)} bytes)")
                            return screenshot_base64
                    except Exception as e:
                        logger.error(f"截取iframe失败: {e}")
                        return None
                
                # 截图canvas
                qrcode_bytes = await qrcode_element.screenshot()
                if qrcode_bytes:
                    # 转换为base64
                    import base64
                    qrcode_base64 = base64.b64encode(qrcode_bytes).decode('utf-8')
                    logger.success(f"✅ 成功获取人脸识别二维码 (大小: {len(qrcode_bytes)} bytes)")
                    return qrcode_base64
                
            except Exception as e:
                logger.error(f"提取人脸识别二维码失败: {e}", exc_info=True)
                return None
                
        except Exception as e:
            logger.debug(f"检测人脸识别失败: {e}")
            return None
    
    async def _click_keep_login_button(self):
        """检测并点击'保持登录'按钮
        
        注意：保持登录弹窗可能在父页面，也可能在登录iframe中
        """
        try:
            # ✅ 先查找所有可能的按钮位置（父页面 + iframe）
            search_targets = []
            
            # 1. 父页面
            search_targets.append(("父页面", self.page))
            
            # 2. 登录iframe
            login_iframe_frames = self.page.frames
            login_iframe = None
            for frame in login_iframe_frames:
                if "alibaba-login-box" in frame.name or "login" in frame.url.lower():
                    login_iframe = frame
                    search_targets.append(("登录iframe", login_iframe))
                    break
            
            # ✅ 导出 HTML 用于分析
            try:
                import os
                from datetime import datetime
                
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                html_dir = os.path.join(os.getcwd(), "screenshots", "html_export")
                os.makedirs(html_dir, exist_ok=True)
                
                # 导出父页面 HTML
                try:
                    parent_html = await self.page.content()
                    parent_html_path = os.path.join(html_dir, f"{timestamp}_parent_page.html")
                    with open(parent_html_path, 'w', encoding='utf-8') as f:
                        f.write(parent_html)
                    logger.info(f"📄 已导出父页面 HTML: {parent_html_path}")
                except Exception as e:
                    logger.warning(f"导出父页面 HTML 失败: {e}")
                
                # 导出 iframe HTML（如果存在）
                if login_iframe:
                    try:
                        iframe_html = await login_iframe.content()
                        iframe_html_path = os.path.join(html_dir, f"{timestamp}_login_iframe.html")
                        with open(iframe_html_path, 'w', encoding='utf-8') as f:
                            f.write(iframe_html)
                        logger.info(f"📄 已导出登录 iframe HTML: {iframe_html_path}")
                    except Exception as e:
                        logger.warning(f"导出 iframe HTML 失败: {e}")
                
                # 导出所有按钮信息（JSON格式，便于分析）
                try:
                    import json
                    buttons_info = []
                    
                    for target_name, target_page in search_targets:
                        try:
                            all_buttons = await target_page.query_selector_all("button")
                            for i, button in enumerate(all_buttons):
                                try:
                                    is_visible = await button.is_visible()
                                    if not is_visible:
                                        continue
                                    
                                    button_text = await button.text_content()
                                    button_html = await button.inner_html()
                                    
                                    # 获取按钮的属性
                                    button_class = await button.get_attribute("class")
                                    button_id = await button.get_attribute("id")
                                    button_type = await button.get_attribute("type")
                                    
                                    # 获取按钮的位置和大小
                                    box = await button.bounding_box()
                                    
                                    buttons_info.append({
                                        "target": target_name,
                                        "index": i,
                                        "text": button_text.strip() if button_text else "",
                                        "class": button_class,
                                        "id": button_id,
                                        "type": button_type,
                                        "html": button_html[:200] if button_html else "",  # 只保存前200字符
                                        "position": {
                                            "x": box["x"] if box else None,
                                            "y": box["y"] if box else None,
                                            "width": box["width"] if box else None,
                                            "height": box["height"] if box else None,
                                        } if box else None
                                    })
                                except Exception as e:
                                    logger.debug(f"获取按钮 {i} 信息失败: {e}")
                                    continue
                        except Exception as e:
                            logger.debug(f"在{target_name}中获取按钮列表失败: {e}")
                    
                    # 保存按钮信息为 JSON
                    buttons_json_path = os.path.join(html_dir, f"{timestamp}_buttons_info.json")
                    with open(buttons_json_path, 'w', encoding='utf-8') as f:
                        json.dump(buttons_info, f, ensure_ascii=False, indent=2)
                    logger.info(f"📊 已导出按钮信息: {buttons_json_path} (共 {len(buttons_info)} 个按钮)")
                    
                except Exception as e:
                    logger.warning(f"导出按钮信息失败: {e}")
                    
            except Exception as e:
                logger.warning(f"导出 HTML 分析文件失败: {e}")
            
            # 查找保持登录按钮的多种选择器
            button_selectors = [
                # 通过文本查找（最可靠）
                "button:has-text('保持')",
                "button:has-text('确认')",
                # 通过类名查找
                "button.keep-login-confirm-btn",
                "button.keep-login-btn",
                "button[class*='keep-login']",
                "button[class*='keep']",
                "button.fm-submit.primary",
                # 通过弹窗结构查找
                "[class*='modal'] button:has-text('保持')",
                "[class*='dialog'] button:has-text('保持')",
                "[class*='popup'] button:has-text('保持')",
                # 通用按钮查找（黄色主按钮通常是"保持"）
                "[class*='primary'] button",
                "button[class*='primary']",
            ]
            
            # 在每个目标位置查找按钮
            for target_name, target_page in search_targets:
                logger.debug(f"在{target_name}中查找保持登录按钮...")
                
                for selector in button_selectors:
                    try:
                        # 查找按钮
                        buttons = await target_page.query_selector_all(selector)
                        
                        for button in buttons:
                            try:
                                # 检查按钮是否可见
                                is_visible = await button.is_visible()
                                if not is_visible:
                                    continue
                                
                                # 获取按钮文本
                                button_text = await button.text_content()
                                button_text = button_text.strip() if button_text else ""
                                
                                logger.debug(f"在{target_name}中找到按钮: '{button_text}'（选择器: {selector}）")
                                
                                # 检查是否是"保持"按钮（排除"不保持"）
                                if button_text and "保持" in button_text:
                                    # 排除"不保持"按钮
                                    if "不保持" in button_text or "不" == button_text:
                                        logger.debug(f"跳过'不保持'按钮: {button_text}")
                                        continue
                                    
                                    logger.info(f"✅ 在{target_name}找到'保持登录'按钮: '{button_text}'")
                                    logger.info(f"点击'{button_text}'按钮...")
                                    
                                    # 点击按钮
                                    await button.click()
                                    await asyncio.sleep(2)
                                    
                                    logger.success(f"✅ 已点击'{button_text}'按钮")
                                    return
                                    
                            except Exception as e:
                                logger.debug(f"检查按钮时出错: {e}")
                                continue
                                
                    except Exception as e:
                        logger.debug(f"选择器 {selector} 在{target_name}中失败: {e}")
                        continue
            
            # 如果都没找到，尝试更通用的方法：查找所有包含"保持"的按钮
            logger.debug("使用通用方法查找'保持'按钮...")
            for target_name, target_page in search_targets:
                try:
                    # 查找所有按钮
                    all_buttons = await target_page.query_selector_all("button")
                    
                    for button in all_buttons:
                        try:
                            if not await button.is_visible():
                                continue
                            
                            button_text = await button.text_content()
                            button_text = button_text.strip() if button_text else ""
                            
                            # 查找包含"保持"但不是"不保持"的按钮
                            if button_text and "保持" in button_text and "不保持" not in button_text:
                                logger.info(f"✅ 在{target_name}通过通用方法找到按钮: '{button_text}'")
                                logger.info(f"点击'{button_text}'按钮...")
                                
                                await button.click()
                                await asyncio.sleep(2)
                                
                                logger.success(f"✅ 已点击'{button_text}'按钮")
                                return
                        except:
                            continue
                except Exception as e:
                    logger.debug(f"在{target_name}中通用查找失败: {e}")
            
            logger.debug("未找到保持登录按钮")
            
        except Exception as e:
            logger.debug(f"点击保持登录按钮失败: {e}")
    
    async def _watch_qrcode_scan(self):
        """异步监控二维码扫描状态（监听iframe请求 + API验证）"""
        logger.info("开始监控二维码扫描状态...")
        start_time = time.time()
        timeout = 600  # 10分钟超时
        check_interval = 2  # 每2秒检查一次
        
        # 重置循环计数器
        self._qr_loop_count = 0
        
        # 设置响应监听器（监听iframe内的JS请求）
        await self._setup_qrcode_response_listener()
        
        # 标记监控状态
        self.state.set("qrcode_scanning", True)
        logger.debug(f"监控参数: timeout={timeout}s, interval={check_interval}s")
        
        try:
            while time.time() - start_time < timeout:
                try:
                    # 检查浏览器是否还在运行
                    if not self.browser or not self.page:
                        logger.info("浏览器已关闭，停止监控")
                        break
                    
                    # 先检查二维码是否过期（轻量级，不需要API调用）
                    is_expired = await self._check_qrcode_expired()
                    if is_expired:
                        logger.warning("二维码已过期或浏览器已关闭")
                        break
                    
                    # 1️⃣ 检查是否监听到扫码成功（通过响应监听器）
                    if self._qrcode_scan_detected:
                        logger.info("✅ 监听到扫码确认，开始处理登录流程...")
                        logger.debug(f"当前标志状态: _qrcode_scan_detected={self._qrcode_scan_detected}")
                        
                        # 重置标志
                        self._qrcode_scan_detected = False
                        
                        try:
                            # 等待5秒，让iframe完成跳转
                            logger.info("等待5秒，让iframe完成登录跳转...")
                            await asyncio.sleep(5)
                            
                            # 📸 截图查看当前页面状态
                            logger.info("截图查看页面状态...")
                            try:
                                await self.capture_screenshot("qrcode_login", "after_confirm")
                            except Exception as e:
                                logger.warning(f"截图失败: {e}")
                            
                            # 🔍 检测是否需要人脸识别
                            logger.info("检测是否需要人脸识别...")
                            face_qrcode = await self._check_face_verification()
                            
                            if face_qrcode:
                                logger.warning("⚠️ 需要人脸识别，发送人脸识别二维码给前端...")
                                # 发送人脸识别二维码给前端
                                if self.tcp_client:
                                    await self.tcp_client.send(
                                        CommandRes.Login().Success(
                                            msg="需要人脸识别，请使用闲鱼APP扫描",
                                            data={
                                                "type": "face_verification",
                                                "qrcode": face_qrcode
                                            }
                                        )
                                    )
                                
                                # 等待用户完成人脸识别，智能检测登录状态
                                logger.info("等待用户完成人脸识别，智能检测登录状态...")
                                face_verify_timeout = 300  # 最多等待5分钟
                                face_verify_start = time.time()
                                 
                                while time.time() - face_verify_start < face_verify_timeout:
                                    await asyncio.sleep(2)  # 每2秒检查一次
                                    
                                    # 1️⃣ 检查登录状态（使用has_login）- 最可靠的方法
                                    try:
                                        from modules.goofish_api import GoofishAPIClient
                                        api_client = GoofishAPIClient(self.state, self)
                                        has_login_result = await api_client.has_login()
                                        await api_client.close()
                                        
                                        # ✅ 检查is_logged_in字段
                                        if has_login_result and has_login_result.get("is_logged_in"):
                                            logger.success("✅ 人脸识别完成，已检测到登录状态（has_login）")
                                            break
                                    except Exception as e:
                                        logger.debug(f"检测has_login失败: {e}")
                                    
                                    # 2️⃣ 检查登录框是否消失 - 辅助判断
                                    try:
                                        login_box = await self.page.query_selector("#alibaba-login-box")
                                        if not login_box:
                                            logger.success("✅ 人脸识别完成，登录框已消失")
                                            break
                                    except Exception as e:
                                        logger.debug(f"检测登录框失败: {e}")
                                    
                                    logger.debug(f"人脸识别中...已等待{int(time.time() - face_verify_start)}秒")
                                 
                                # 检查是否超时
                                if time.time() - face_verify_start >= face_verify_timeout:
                                    logger.warning("⚠️ 人脸识别等待超时（60秒），继续验证登录状态")
                                
                                # 截图查看状态
                                try:
                                    await self.capture_screenshot("qrcode_login", "after_face_verify")
                                except:
                                    pass
                            
                            # 🔍 检测并点击"保持登录"按钮
                            logger.info("检测是否需要点击'保持登录'...")
                            await self._click_keep_login_button()
                            
                            logger.info("等待完成，主动刷新父页面以应用cookie...")
                            
                            # 主动刷新父页面，触发cookie生效
                            try:
                                await self.page.reload(
                                    wait_until="domcontentloaded",  # 降级
                                    timeout=30000  # 30秒
                                )
                                await asyncio.sleep(3)
                                logger.debug("页面已刷新，开始验证登录状态...")
                            except Exception as e:
                                logger.warning(f"页面刷新失败: {e}，继续验证")
                            
                            # ✅ 修复：刷新后重试验证登录，给cookie时间生效
                            logger.info("验证登录状态（最多重试10次，每次间隔3秒）...")
                            login_verified = False
                            max_retries = 10
                            retry_interval = 3
                            
                            for retry in range(max_retries):
                                try:
                                    from modules.goofish_api import GoofishAPIClient
                                    api_client = GoofishAPIClient(self.state, self)
                                    login_result = await api_client.has_login()
                                    await api_client.close()
                                    
                                    # ✅ 检查is_logged_in字段
                                    if login_result and login_result.get("is_logged_in"):
                                        logger.success(f"🎉 二维码扫码登录成功！（第{retry + 1}次验证）")
                                        login_verified = True
                                        break
                                    else:
                                        logger.debug(f"第{retry + 1}/{max_retries}次验证：登录未完成，{retry_interval}秒后重试...")
                                        if retry < max_retries - 1:  # 不是最后一次才等待
                                            await asyncio.sleep(retry_interval)
                                except Exception as e:
                                    logger.warning(f"第{retry + 1}/{max_retries}次验证失败: {e}")
                                    if retry < max_retries - 1:
                                        await asyncio.sleep(retry_interval)
                            
                            # 检查验证结果
                            if login_verified:
                                # 完整验证登录状态（获取用户信息）
                                await self.check_login()
                                
                                # ⚠️ 防止重复通知
                                if not self._login_success_notified:
                                    self._login_success_notified = True
                                    
                                    # 发送登录成功通知
                                    if self.tcp_client:
                                        try:
                                            account = self.state.get("account") or "User"
                                            cookies = self.state.get("cookie") or {}
                                            
                                            login_success_msg = CommandRes.Login().Success(
                                                msg="登录成功",
                                                data={
                                                    "type": "qrcode",
                                                    "account": account,
                                                    "logged_in": True,
                                                    "cookie_count": len(cookies)
                                                }
                                            )
                                            await self.tcp_client.send(login_success_msg)
                                            logger.info(f"✅ 已发送登录成功通知，账号: {account}")
                                            
                                            await asyncio.sleep(0.5)
                                            await self.tcp_client.send_cookie_update()
                                            logger.info("✅ 已发送cookie更新")
                                        except Exception as e:
                                            logger.error(f"发送登录成功通知失败: {e}")
                                    
                                # 退出监控循环
                                break
                            else:
                                logger.warning(f"⚠️ 登录验证失败（重试{max_retries}次后仍未成功），退出监控")
                                break
                        except Exception as e:
                            logger.error(f"等待过程异常: {e}", exc_info=True)
                            break
                    else:
                        # 调试日志：每10次循环输出一次
                        loop_count = getattr(self, '_qr_loop_count', 0)
                        self._qr_loop_count = loop_count + 1
                        # ✅ 移除冗余日志：监控循环是高频操作
                        # if self._qr_loop_count % 5 == 0:
                        #     logger.debug(f"监控循环运行中（第{self._qr_loop_count}次），等待扫码确认...")
                    
                    # ✅ 不再在循环中调用 check_login()，避免未登录时的误判和刷屏
                    # 只在监听到 CONFIRMED 状态后才验证登录（已在上面的逻辑中处理）
                    # 未检测到扫码确认时，仅等待不做任何检查
                    if False:  # 占位，保持代码结构
                        logger.success("🎉 二维码扫码登录成功！")
                        
                        # ⚠️ 防止重复通知：检查是否已经发送过
                        if not self._login_success_notified:
                            self._login_success_notified = True
                            
                            # 主动向控制端发送登录成功通知和状态更新
                            if self.tcp_client:
                                try:
                                    # 获取账号信息
                                    account = self.state.get("account") or "User"
                                    cookies = self.state.get("cookie") or {}
                                    
                                    # 发送登录成功消息
                                    login_success_msg = CommandRes.Login().Success(
                                        msg="登录成功",
                                        data={
                                            "type": "qrcode",
                                            "account": account,
                                            "logged_in": True,
                                            "cookie_count": len(cookies)
                                        }
                                    )
                                    await self.tcp_client.send(login_success_msg)
                                    logger.info(f"✅ 已发送登录成功通知到控制端，账号: {account}, Cookie数: {len(cookies)}")
                                    
                                    # 延迟一下再发送cookie更新，确保登录消息先到达
                                    await asyncio.sleep(0.5)
                                    await self.tcp_client.send_cookie_update()
                                    logger.info("✅ 已发送cookie更新到控制端")
                                except Exception as e:
                                    logger.error(f"发送登录成功通知失败: {e}")
                            else:
                                logger.warning("TCP客户端未设置，无法发送登录成功通知")
                        else:
                            logger.debug("登录成功通知已发送过，跳过重复通知")
                        
                        break
                    
                    # 未登录，继续等待
                    await asyncio.sleep(check_interval)
                    
                except Exception as e:
                    # 捕获浏览器关闭等异常
                    error_msg = str(e).lower()
                    if 'closed' in error_msg or 'target' in error_msg:
                        logger.debug("浏览器已关闭，停止监控")
                        break
                    else:
                        logger.error(f"监控循环异常: {e}", exc_info=True)
                        await asyncio.sleep(check_interval)
            
            # 检查是否超时
            if time.time() - start_time >= timeout:
                logger.warning("二维码扫码超时")
                self.state.set_login_error("扫码超时")
                
        except asyncio.CancelledError:
            logger.info("监控任务被取消")
            # ✅ 任务被取消时，重置登录状态（允许重新开始登录）
            if self.state.get("login_status") == "logging_in":
                self.state.update(login_status="not_logged_in")
                logger.debug("监控任务被取消，已重置登录状态")
        except Exception as e:
            logger.error(f"监控任务异常退出: {e}")
            # ✅ 任务异常时，重置登录状态（允许重新开始登录）
            if self.state.get("login_status") == "logging_in":
                self.state.update(login_status="not_logged_in")
                logger.debug("监控任务异常，已重置登录状态")
        finally:
            # 移除响应监听器
            await self._remove_qrcode_response_listener()
            self.state.set("qrcode_scanning", False)
            logger.info("二维码扫描监控任务结束")
    
    async def _check_qrcode_expired(self):
        """检查二维码是否过期"""
        try:
            # 检查浏览器和页面是否还存在
            if not self.browser or not self.page or self.page.is_closed():
                logger.debug("浏览器或页面已关闭，停止检查")
                return True  # 返回True以停止监控循环
            
            iframe_element = await self.page.query_selector("#alibaba-login-box")
            if not iframe_element:
                return False
            
            iframe = await iframe_element.content_frame()
            if not iframe:
                return False
            
            error_div = await iframe.query_selector(".qrcode-error")
            
            if error_div:
                # 检查是否可见（display != none）
                is_visible = await error_div.is_visible()
                return is_visible
            
            return False
        except Exception as e:
            # 如果是浏览器关闭导致的错误，静默处理
            error_msg = str(e).lower()
            if 'closed' in error_msg or 'target' in error_msg:
                logger.debug("浏览器已关闭，停止二维码过期检查")
                return True  # 停止循环
            else:
                logger.debug(f"检查二维码过期状态失败: {e}")
            return False

    async def get_qrcode_base64(self):
        """获取登录二维码的base64"""
        try:
            # 检查浏览器状态
            if not self.browser or not self.page:
                logger.error("浏览器已关闭，无法获取二维码")
                return None
            
            # iframe应该已经加载完成（在handle_qrcode_login中已等待）
            iframe_element = await self.page.query_selector("#alibaba-login-box")
            if not iframe_element:
                logger.error("未找到登录iframe元素")
                return None
            
            logger.info("获取iframe内容框架...")
            iframe = await iframe_element.content_frame()
            if not iframe:
                logger.error("iframe content_frame为空")
                return None
            
            # 等待二维码canvas元素出现（增加超时时间）
            logger.info("等待二维码canvas元素...")
            try:
                await iframe.wait_for_selector("#qrcode-img canvas", timeout=10000)
                logger.info("二维码canvas元素已出现")
            except Exception as e:
                logger.error(f"等待二维码canvas超时: {e}")
                # 📸 超时时自动截图
                await self.capture_screenshot("login", "wait_qrcode_canvas_timeout")
                # 尝试查看iframe中有什么元素
                try:
                    html = await iframe.content()
                    logger.debug(f"iframe HTML (前500字符): {html[:500]}")
                except:
                    pass
                return None
            
            # 获取canvas二维码
            canvas = await iframe.query_selector("#qrcode-img canvas")
            if not canvas:
                logger.error("未找到二维码canvas元素")
                return None
            
            # 检查canvas是否被错误遮罩遮挡
            error_div = await iframe.query_selector(".qrcode-error")
            if error_div:
                is_visible = await error_div.is_visible()
                if is_visible:
                    logger.warning("二维码已过期，需要刷新")
                    return None
            
            # 截取二维码图片
            logger.info("截取二维码图片...")
            buf = await canvas.screenshot()
            qr_base64 = base64.b64encode(buf).decode()
            logger.success(f"成功获取二维码 (大小: {len(qr_base64)} bytes)")
            return qr_base64
                
        except Exception as e:
            logger.error(f"获取二维码失败: {e}")
            import traceback
            logger.error(f"错误堆栈: {traceback.format_exc()}")
            return None

    async def wait_qrcode_scan(self, timeout=180):
        """等待二维码扫描完成"""
        start_time = time.time()
        while time.time() - start_time < timeout:
            is_logged = await self.check_login()
            if is_logged:
                logger.success("二维码扫码登录成功")
                return True
            await asyncio.sleep(2)
        
        logger.warning("二维码扫码超时")
        self.state.set_login_error("扫码超时")
        return False

    # ----------------------------------------------------------------------
    # 短信登录
    # ----------------------------------------------------------------------
    async def handle_sms_login(self, msg):
        """处理短信验证码登录"""
        try:
            # 检查浏览器是否已启动
            if not self.browser or not self.page:
                logger.error("浏览器未启动，无法进行手机号登录")
                return CommandRes.Login().Fail(msg="浏览器未启动，请先启动浏览器")
            
            phone = msg.data.get("phone") if msg and msg.data else None
            code = msg.data.get("code") if msg and msg.data else None
            
            if not phone:
                return CommandRes.Login().Fail(msg="请提供手机号")
            
            # ⚠️ 如果是第一步（发送验证码），先检查是否已经登录
            if not code:
                logger.debug("检查当前登录状态...")
                is_logged = await self.check_login()
                if is_logged:
                    logger.success(f"✅ 账号已登录: {self.state.get('account')}，无需再次登录")
                    return CommandRes.Login().Success(
                        msg="账号已登录",
                        data={"account": self.state.get('account'), "type": "already_logged"}
                    )
            
            # 重置登录通知标记（开始新的登录流程）
            self._login_success_notified = False
            self.state.start_login(account_name=phone)
            
            if not code:
                # 第一步：发送验证码
                # 访问personal页面（会自动弹出登录iframe）
                await self.page.goto("https://www.goofish.com/personal", timeout=15000, wait_until="domcontentloaded")
                logger.info("已访问personal页面，等待登录iframe加载")
                await asyncio.sleep(2)
                
                success = await self._send_sms_code(phone)
                if success:
                    return CommandRes.Login().Success(
                        msg="验证码已发送",
                        data={"type": "sms", "phone": phone, "wait_code": True}
                    )
                else:
                    self.state.set_login_error("发送验证码失败")
                    return CommandRes.Login().Fail(msg="发送验证码失败")
            else:
                # 第二步：提交验证码登录
                # ✅ 不重新访问页面，直接使用已有的iframe（避免丢失已填写的手机号）
                logger.info(f"准备提交验证码（手机号: {phone}）")
                success = await self._submit_sms_code(phone, code)
                if success:
                    # ✅ 等待Cookie和Session完全生效（登录成功后需要时间建立Session）
                    logger.info("登录成功，等待3秒让Cookie和Session生效...")
                    await asyncio.sleep(3)
                    
                    await self.check_login()
                    
                    # ⚠️ 防止重复通知：使用标记位
                    if not self._login_success_notified:
                        self._login_success_notified = True
                        
                        # 主动发送状态更新到控制端
                        if self.tcp_client:
                            try:
                                await self.tcp_client.send_cookie_update()
                                logger.info("✅ 已发送cookie更新到控制端")
                            except Exception as e:
                                logger.warning(f"发送状态更新失败: {e}")
                    else:
                        logger.debug("短信登录成功通知已发送过，跳过重复通知")
                    
                    return CommandRes.Login().Success(
                        msg="登录成功",
                        data={"account": self.state.get("account")}
                    )
                else:
                    self.state.set_login_error("验证码错误或登录失败")
                    return CommandRes.Login().Fail(msg="验证码错误或登录失败")
                    
        except Exception as e:
            logger.error(f"短信登录失败: {e}")
            self.state.set_login_error(str(e))
            return CommandRes.Login().Fail(msg=f"短信登录失败: {e}")

    # 发送短信验证码
    async def _send_sms_code(self, phone):
        """发送短信验证码"""
        try:
            logger.info(f"向手机号 {phone} 发送验证码")
            
            # 等待iframe加载
            await self.page.wait_for_selector("#alibaba-login-box", timeout=10000)
            
            # 获取iframe
            iframe_element = await self.page.query_selector("#alibaba-login-box")
            iframe = await iframe_element.content_frame()
            
            # 确保在短信登录标签
            await iframe.click(".sms-login-tab-item", timeout=5000)
            await asyncio.sleep(0.5)
            
            # 勾选协议
            agreement_checkbox = await iframe.query_selector("#fm-agreement-checkbox")
            if agreement_checkbox:
                is_checked = await agreement_checkbox.is_checked()
                if not is_checked:
                    await iframe.click("#fm-agreement-checkbox")
                    await asyncio.sleep(0.3)
            
            # 输入手机号
            await iframe.fill("#fm-sms-login-id", phone)
            await asyncio.sleep(0.5)
            
            # 点击获取验证码按钮
            await iframe.click(".send-btn-link")
            logger.info("验证码发送成功")
            
            return True
        except Exception as e:
            logger.error(f"发送短信验证码失败: {e}")
            return False

    # 提交短验证码登录
    async def _submit_sms_code(self, phone, code):
        """提交短信验证码进行登录"""
        try:
            logger.info(f"提交验证码: {code}")
            
            # 获取iframe
            iframe_element = await self.page.query_selector("#alibaba-login-box")
            iframe = await iframe_element.content_frame()
            
            # 输入验证码
            await iframe.fill("#fm-smscode", code)
            await asyncio.sleep(0.5)
            
            # 点击登录按钮
            await iframe.click("button.fm-submit.sms-login")
            
            # ✅ 关键：等待登录iframe消失（说明登录流程完成）
            logger.info("等待登录iframe关闭...")
            max_wait = 15
            iframe_closed = False
            
            for waited in range(1, max_wait + 1):
                await asyncio.sleep(1)
                
                try:
                    # 检查iframe是否还存在
                    iframe_element = await self.page.query_selector("#alibaba-login-box")
                    if not iframe_element:
                        logger.info(f"登录iframe已关闭 ({waited}秒)")
                        iframe_closed = True
                        break
                    else:
                        logger.debug(f"等待中... ({waited}秒)")
                except:
                    # 查询失败，可能iframe已经不存在了
                    logger.info(f"登录iframe已关闭 ({waited}秒)")
                    iframe_closed = True
                    break
            
            if not iframe_closed:
                logger.warning("等待登录iframe关闭超时（15秒）")
                # 📸 超时时自动截图
                await self.capture_screenshot("login", "wait_iframe_close_timeout")
                
                # 调试：输出iframe的内容，看看到底是什么
                try:
                    iframe_element = await self.page.query_selector("#alibaba-login-box")
                    if iframe_element:
                        iframe_html = await iframe_element.inner_html()
                        logger.debug(f"iframe仍然存在，内容片段: {iframe_html[:500]}")
                        
                        # 检查iframe中是否有"选择账号"相关内容
                        if "选择" in iframe_html and "账号" in iframe_html:
                            logger.warning("⚠️ iframe中可能是账号选择页面！")
                            
                            # 尝试在iframe中查找登录按钮并点击
                            try:
                                iframe = await iframe_element.content_frame()
                                if iframe:
                                    login_btn = await iframe.query_selector(".login-btn")
                                    if login_btn:
                                        logger.info("在iframe中找到登录按钮，尝试点击...")
                                        await login_btn.click()
                                        logger.success("已点击iframe中的登录按钮")
                                        await asyncio.sleep(3)  # 等待跳转
                                        iframe_closed = True  # 标记为已处理
                                    else:
                                        logger.warning("iframe中未找到.login-btn按钮")
                            except Exception as btn_error:
                                logger.error(f"点击iframe登录按钮失败: {btn_error}")
                except Exception as e:
                    logger.debug(f"获取iframe内容失败: {e}")
            
            # 等待页面稳定（给一点时间让页面完全加载）
            await asyncio.sleep(2)
            
            # 现在获取最终页面状态
            current_url = self.page.url
            page_title = await self.page.title()
            logger.info(f"最终页面状态 - URL: {current_url}")
            logger.info(f"最终页面状态 - 标题: {page_title}")
            
            # 调试：输出页面内容片段
            try:
                page_text = await self.page.text_content("body")
                if page_text:
                    # 截取前500个字符，看看页面内容
                    preview = page_text[:500].replace('\n', ' ').replace('\r', '')
                    logger.debug(f"页面文本内容预览: {preview}")
                    
                    # 检查是否包含"选择"、"账号"等关键字
                    if "选择" in page_text and "账号" in page_text:
                        logger.warning("⚠️ 页面内容包含'选择'和'账号'关键字，可能是账号选择页面")
            except Exception as e:
                logger.debug(f"获取页面文本失败: {e}")
            
            # 方法1: 检查URL和标题
            is_account_select_by_url = "login_token_login" in current_url
            is_account_select_by_title = any(keyword in page_title for keyword in ["选择账号", "选择闲鱼账号", "选择登录账号"])
            
            # 方法2: 尝试查找登录按钮元素（更可靠）
            # 注意：登录按钮是 <div class="login-btn">，不是 <button>！
            possible_selectors = [
                ".login-btn",           # ✅ 直接用class，不限定标签
                "div.login-btn",        # div元素
                "[class*='login-btn']", # 包含login-btn的class
                "button:has-text('登录')",  # 文本为"登录"的button（备用）
            ]
            login_btn = None
            for selector in possible_selectors:
                try:
                    login_btn = await self.page.query_selector(selector)
                    if login_btn:
                        logger.info(f"✅ 找到账号选择按钮，选择器: {selector}")
                        break
                except:
                    continue
            is_account_select_by_element = login_btn is not None
            
            # 综合判断
            is_account_select = is_account_select_by_url or is_account_select_by_title or is_account_select_by_element
            
            logger.info(f"账号选择页面检测: URL={is_account_select_by_url}, 标题={is_account_select_by_title}, 元素={is_account_select_by_element}, 最终={is_account_select}")
            
            if is_account_select:
                logger.info("✅ 检测到账号选择页面，准备自动选择第一个账号")
                try:
                    # 如果已经找到按钮，直接点击；否则尝试所有可能的选择器
                    if login_btn:
                        await login_btn.click()
                        logger.success("已点击账号选择页面的【登录】按钮")
                    else:
                        # 尝试多个可能的选择器
                        clicked = False
                        for selector in possible_selectors:
                            try:
                                await self.page.wait_for_selector(selector, timeout=3000)
                                await self.page.click(selector)
                                logger.success(f"已点击账号选择页面的【登录】按钮 (选择器: {selector})")
                                clicked = True
                                break
                            except:
                                continue
                        
                        if not clicked:
                            logger.error("❌ 未找到可点击的登录按钮")
                            # 输出页面HTML帮助调试
                            page_content = await self.page.content()
                            logger.debug(f"当前页面HTML:\n{page_content}")
                            raise Exception("未找到账号选择页面的登录按钮")
                    
                    # 等待最终登录完成
                    await asyncio.sleep(5)
                    
                    # 再次检查URL，确认是否跳转成功
                    final_url = self.page.url
                    logger.info(f"选择账号后 - 最终URL: {final_url}")
                except Exception as e:
                    logger.error(f"❌ 处理账号选择页面失败: {e}")
                    raise
            
            logger.success("验证码提交成功")
            return True
        except Exception as e:
            logger.error(f"提交验证码失败: {e}")
            return False